home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / nsExtensionManager.js < prev    next >
Text File  |  2007-10-12  |  328KB  |  8,588 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Extension Manager.
  16.  *
  17.  * The Initial Developer of the Original Code is Ben Goodger.
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Google Inc.)
  23.  *  Benjamin Smedberg <benjamin@smedbergs.us>
  24.  *  Jens Bannmann <jens.b@web.de>
  25.  *  Robert Strong <robert.bugzilla@gmail.com>
  26.  *  Dave Townsend <dave.townsend@blueprintit.co.uk>
  27.  *  Daniel Veditz <dveditz@mozilla.com>
  28.  *
  29.  * Alternatively, the contents of this file may be used under the terms of
  30.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  31.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  32.  * in which case the provisions of the GPL or the LGPL are applicable instead
  33.  * of those above. If you wish to allow use of your version of this file only
  34.  * under the terms of either the GPL or the LGPL, and not to allow others to
  35.  * use your version of this file under the terms of the MPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the notice
  37.  * and other provisions required by the GPL or the LGPL. If you do not delete
  38.  * the provisions above, a recipient may use your version of this file under
  39.  * the terms of any one of the MPL, the GPL or the LGPL.
  40.  *
  41.  * ***** END LICENSE BLOCK ***** */
  42.  
  43. //
  44. // TODO:
  45. // - better logging
  46. //
  47.  
  48. const nsIExtensionManager             = Components.interfaces.nsIExtensionManager;
  49. const nsIAddonUpdateCheckListener     = Components.interfaces.nsIAddonUpdateCheckListener;
  50. const nsIUpdateItem                   = Components.interfaces.nsIUpdateItem;
  51. const nsILocalFile                    = Components.interfaces.nsILocalFile;
  52. const nsILineInputStream              = Components.interfaces.nsILineInputStream;
  53. const nsIInstallLocation              = Components.interfaces.nsIInstallLocation;
  54. const nsIURL                          = Components.interfaces.nsIURL
  55. // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  56. // it has been removed will cause a crash on Mac OS X - bug 292823
  57. const nsIDirectoryEnumerator          = Components.interfaces.nsIDirectoryEnumerator;
  58.  
  59. const PREF_EM_CHECK_COMPATIBILITY     = "extensions.checkCompatibility";
  60. const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
  61. const PREF_UPDATE_COUNT               = "extensions.update.count";
  62. const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
  63. const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
  64. const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
  65. const PREF_EM_LAST_SELECTED_SKIN      = "extensions.lastSelectedSkin";
  66. const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
  67. const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
  68. const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
  69. const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
  70. const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
  71. const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
  72. const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
  73. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
  74. const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
  75. const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
  76. const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
  77. const PREF_BLOCKLIST_DETAILS_URL      = "extensions.blocklist.detailsURL";
  78. const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
  79. const PREF_BLOCKLIST_INTERVAL         = "extensions.blocklist.interval";
  80. const PREF_UPDATE_NOTIFYUSER          = "extensions.update.notifyUser";
  81.  
  82. const DIR_EXTENSIONS                  = "extensions";
  83. const DIR_CHROME                      = "chrome";
  84. const DIR_STAGE                       = "staged-xpis";
  85. const FILE_EXTENSIONS                 = "extensions.rdf";
  86. const FILE_EXTENSION_MANIFEST         = "extensions.ini";
  87. const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
  88. const FILE_AUTOREG                    = ".autoreg";
  89. const FILE_INSTALL_MANIFEST           = "install.rdf";
  90. const FILE_CONTENTS_MANIFEST          = "contents.rdf";
  91. const FILE_CHROME_MANIFEST            = "chrome.manifest";
  92. const FILE_BLOCKLIST                  = "blocklist.xml";
  93.  
  94. const UNKNOWN_XPCOM_ABI               = "unknownABI";
  95.  
  96. const FILE_LOGFILE                    = "extensionmanager.log";
  97.  
  98. const FILE_DEFAULT_THEME_JAR          = "classic.jar";
  99. const TOOLKIT_ID                      = "toolkit@mozilla.org"
  100.  
  101. const KEY_PROFILEDIR                  = "ProfD";
  102. const KEY_PROFILEDS                   = "ProfDS";
  103. const KEY_APPDIR                      = "XCurProcD";
  104. const KEY_TEMPDIR                     = "TmpD";
  105.  
  106. const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
  107. const EM_ITEM_INSTALLED               = "item-installed";
  108. const EM_ITEM_UPGRADED                = "item-upgraded";
  109. const EM_ITEM_UNINSTALLED             = "item-uninstalled";
  110. const EM_ITEM_ENABLED                 = "item-enabled";
  111. const EM_ITEM_DISABLED                = "item-disabled";
  112. const EM_ITEM_CANCEL                  = "item-cancel-action";
  113.  
  114. const OP_NONE                         = "";
  115. const OP_NEEDS_INSTALL                = "needs-install";
  116. const OP_NEEDS_UPGRADE                = "needs-upgrade";
  117. const OP_NEEDS_UNINSTALL              = "needs-uninstall";
  118. const OP_NEEDS_ENABLE                 = "needs-enable";
  119. const OP_NEEDS_DISABLE                = "needs-disable";
  120.  
  121. const KEY_APP_PROFILE                 = "app-profile";
  122. const KEY_APP_GLOBAL                  = "app-global";
  123.  
  124. const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
  125.  
  126. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  127. const PREFIX_NS_CHROME                = "http://www.mozilla.org/rdf/chrome#";
  128. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  129. const PREFIX_EXTENSION                = "urn:mozilla:extension:";
  130. const PREFIX_THEME                    = "urn:mozilla:theme:";
  131. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  132. const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
  133. const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{b01bf10c-302a-11da-b67b-000d60ca027b}";
  134. const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
  135. const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
  136.  
  137. const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
  138. const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
  139. const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  140. const URI_FINALIZE_DIALOG             = "chrome://mozapps/content/extensions/finalize.xul";
  141. const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
  142. const URI_FLOCKKIT_PROPERTIES         = "chrome://flock/locale/toolkit/flockOverrides.properties";
  143. const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
  144. const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
  145. const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
  146. const URI_EXTENSION_LIST_DIALOG       = "chrome://mozapps/content/extensions/list.xul";
  147.  
  148. const INSTALLERROR_SUCCESS               = 0;
  149. const INSTALLERROR_INVALID_VERSION       = -1;
  150. const INSTALLERROR_INVALID_GUID          = -2;
  151. const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
  152. const INSTALLERROR_PHONED_HOME           = -4;
  153. const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
  154. const INSTALLERROR_BLOCKLISTED           = -6;
  155.  
  156. const MODE_RDONLY   = 0x01;
  157. const MODE_WRONLY   = 0x02;
  158. const MODE_CREATE   = 0x08;
  159. const MODE_APPEND   = 0x10;
  160. const MODE_TRUNCATE = 0x20;
  161.  
  162. const PERMS_FILE      = 0644;
  163. const PERMS_DIRECTORY = 0755;
  164.  
  165. var firefoxAppID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
  166. var firefoxVersion = "2.0.0.7";
  167.  
  168. var gApp  = null;
  169. var gPref = null;
  170. var gRDF  = null;
  171. var gOS   = null;
  172. var gXPCOMABI             = null;
  173. var gOSTarget             = null;
  174. var gConsole              = null;
  175. var gInstallManifestRoot  = null;
  176. var gVersionChecker       = null;
  177. var gLoggingEnabled       = null;
  178. var gCheckCompatibility   = true;
  179.  
  180. /** 
  181.  * Valid GUIDs fit this pattern.
  182.  */
  183. var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
  184.  
  185. // shared code for suppressing bad cert dialogs
  186. //@line 40 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/../../shared/src/badCertHandler.js"
  187.  
  188. /**
  189.  * Only allow built-in certs for HTTPS connections.  See bug 340198.
  190.  */
  191. function checkCert(channel) {
  192.   if (!channel.originalURI.schemeIs("https"))  // bypass
  193.     return;
  194.  
  195.   const Ci = Components.interfaces;  
  196.   var cert =
  197.       channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
  198.       SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
  199.  
  200.   var issuer = cert.issuer;
  201.   while (issuer && !cert.equals(issuer)) {
  202.     cert = issuer;
  203.     issuer = cert.issuer;
  204.   }
  205.  
  206.   if (!issuer || issuer.tokenName != "Builtin Object Token")
  207.     throw "cert issuer is not built-in";
  208. }
  209.  
  210. /**
  211.  * This class implements nsIBadCertListener.  It's job is to prevent "bad cert"
  212.  * security dialogs from being shown to the user.  It is better to simply fail
  213.  * if the certificate is bad. See bug 304286.
  214.  */
  215. function BadCertHandler() {
  216. }
  217. BadCertHandler.prototype = {
  218.  
  219.   // nsIBadCertListener
  220.   confirmUnknownIssuer: function(socketInfo, cert, certAddType) {
  221.     LOG("EM BadCertHandler: Unknown issuer");
  222.     return false;
  223.   },
  224.  
  225.   confirmMismatchDomain: function(socketInfo, targetURL, cert) {
  226.     LOG("EM BadCertHandler: Mismatched domain");
  227.     return false;
  228.   },
  229.  
  230.   confirmCertExpired: function(socketInfo, cert) {
  231.     LOG("EM BadCertHandler: Expired certificate");
  232.     return false;
  233.   },
  234.  
  235.   notifyCrlNextupdate: function(socketInfo, targetURL, cert) {
  236.   },
  237.  
  238.   // nsIChannelEventSink
  239.   onChannelRedirect: function(oldChannel, newChannel, flags) {
  240.     // make sure the certificate of the old channel checks out before we follow
  241.     // a redirect from it.  See bug 340198.
  242.     checkCert(oldChannel);
  243.   },
  244.  
  245.   // nsIInterfaceRequestor
  246.   getInterface: function(iid) {
  247.     if (iid.equals(Components.interfaces.nsIBadCertListener) ||
  248.         iid.equals(Components.interfaces.nsIChannelEventSink))
  249.       return this;
  250.  
  251.     Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  252.     return null;
  253.   },
  254.  
  255.   // nsISupports
  256.   QueryInterface: function(iid) {
  257.     if (!iid.equals(Components.interfaces.nsIBadCertListener) &&
  258.         !iid.equals(Components.interfaces.nsIChannelEventSink) &&
  259.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  260.         !iid.equals(Components.interfaces.nsISupports))
  261.       throw Components.results.NS_ERROR_NO_INTERFACE;
  262.     return this;
  263.   }
  264. };
  265. //@line 187 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  266.  
  267. /**
  268.  * Creates a Version Checker object.
  269.  * @returns A handle to the global Version Checker service.
  270.  */
  271. function getVersionChecker() {
  272.   if (!gVersionChecker) {
  273.     gVersionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
  274.                                 .getService(Components.interfaces.nsIVersionComparator);
  275.   }
  276.   return gVersionChecker;
  277. }
  278.  
  279. var BundleManager = { 
  280.   /**
  281.   * Creates and returns a String Bundle at the specified URI
  282.   * @param   bundleURI
  283.   *          The URI of the bundle to load
  284.   * @returns A nsIStringBundle which was retrieved.
  285.   */
  286.   getBundle: function(bundleURI) {
  287.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  288.                         .getService(Components.interfaces.nsIStringBundleService);
  289.     return sbs.createBundle(bundleURI);
  290.   },
  291.   
  292.   _appName: "",
  293.   
  294.   /**
  295.    * The Application's display name.
  296.    */
  297.   get appName() {
  298.     if (!this._appName) {
  299.       var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
  300.       this._appName = brandBundle.GetStringFromName("brandShortName");
  301.     }
  302.     return this._appName;
  303.   }
  304. };
  305.  
  306. ///////////////////////////////////////////////////////////////////////////////
  307. //
  308. // Utility Functions
  309. //
  310. function EM_NS(property) {
  311.   return PREFIX_NS_EM + property;
  312. }
  313.  
  314. function CHROME_NS(property) {
  315.   return PREFIX_NS_CHROME + property;
  316. }
  317.  
  318. function EM_R(property) {
  319.   return gRDF.GetResource(EM_NS(property));
  320. }
  321.  
  322. function EM_L(literal) {
  323.   return gRDF.GetLiteral(literal);
  324. }
  325.  
  326. function EM_I(integer) {
  327.   return gRDF.GetIntLiteral(integer);
  328. }
  329.  
  330. function EM_D(integer) {
  331.   return gRDF.GetDateLiteral(integer);
  332. }
  333.  
  334. /**
  335.  * Gets a preference value, handling the case where there is no default.
  336.  * @param   func
  337.  *          The name of the preference function to call, on nsIPrefBranch
  338.  * @param   preference
  339.  *          The name of the preference
  340.  * @param   defaultValue
  341.  *          The default value to return in the event the preference has 
  342.  *          no setting
  343.  * @returns The value of the preference, or undefined if there was no
  344.  *          user or default value.
  345.  */
  346. function getPref(func, preference, defaultValue) {
  347.   try {
  348.     return gPref[func](preference);
  349.   }
  350.   catch (e) {
  351.   }
  352.   return defaultValue;
  353. }
  354.  
  355. /**
  356.  * Initializes a RDF Container at a URI in a datasource.
  357.  * @param   datasource
  358.  *          The datasource the container is in
  359.  * @param   root
  360.  *          The RDF Resource which is the root of the container.
  361.  * @returns The nsIRDFContainer, initialized at the root.
  362.  */
  363. function getContainer(datasource, root) {
  364.   var ctr = Components.classes["@mozilla.org/rdf/container;1"]
  365.                       .createInstance(Components.interfaces.nsIRDFContainer);
  366.   ctr.Init(datasource, root);
  367.   return ctr;
  368. }
  369.  
  370. /**
  371.  * Gets a RDF Resource for item with the given ID
  372.  * @param   id
  373.  *          The GUID of the item to construct a RDF resource to the 
  374.  *          active item for
  375.  * @returns The RDF Resource to the Active item. 
  376.  */
  377. function getResourceForID(id) {
  378.   return gRDF.GetResource(PREFIX_ITEM_URI + id);
  379. }
  380.  
  381. /**
  382.  * Construct a nsIUpdateItem with the supplied metadata
  383.  * ...
  384.  */
  385. function makeItem(id, version, locationKey, minVersion, maxVersion, name, 
  386.                   updateURL, updateHash, iconURL, updateRDF, type) {
  387.   var item = Components.classes["@mozilla.org/updates/item;1"]
  388.                        .createInstance(Components.interfaces.nsIUpdateItem);
  389.   item.init(id, version, locationKey, minVersion, maxVersion, name,
  390.             updateURL, updateHash, iconURL, updateRDF, type);
  391.   return item;
  392. }
  393.  
  394. /**
  395.  * Gets the specified directory at the speciifed hierarchy under a 
  396.  * Directory Service key. 
  397.  * @param   key
  398.  *          The Directory Service Key to start from
  399.  * @param   pathArray
  400.  *          An array of path components to locate beneath the directory 
  401.  *          specified by |key|
  402.  * @return  nsIFile object for the location specified. If the directory
  403.  *          requested does not exist, it is created, along with any
  404.  *          parent directories that need to be created.
  405.  */
  406. function getDir(key, pathArray) {
  407.   return getDirInternal(key, pathArray, true);
  408. }
  409.  
  410. /**
  411.  * Gets the specified directory at the speciifed hierarchy under a 
  412.  * Directory Service key. 
  413.  * @param   key
  414.  *          The Directory Service Key to start from
  415.  * @param   pathArray
  416.  *          An array of path components to locate beneath the directory 
  417.  *          specified by |key|
  418.  * @return  nsIFile object for the location specified. If the directory
  419.  *          requested does not exist, it is NOT created.
  420.  */
  421. function getDirNoCreate(key, pathArray) {
  422.   return getDirInternal(key, pathArray, false);
  423. }
  424.  
  425. /**
  426.  * Gets the specified directory at the speciifed hierarchy under a 
  427.  * Directory Service key. 
  428.  * @param   key
  429.  *          The Directory Service Key to start from
  430.  * @param   pathArray
  431.  *          An array of path components to locate beneath the directory 
  432.  *          specified by |key|
  433.  * @param   shouldCreate
  434.  *          true if the directory hierarchy specified in |pathArray|
  435.  *          should be created if it does not exist,
  436.  *          false otherwise.
  437.  * @return  nsIFile object for the location specified. 
  438.  */
  439. function getDirInternal(key, pathArray, shouldCreate) {
  440.   var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
  441.                               .getService(Components.interfaces.nsIProperties);
  442.   var dir = fileLocator.get(key, nsILocalFile);
  443.   for (var i = 0; i < pathArray.length; ++i) {
  444.     dir.append(pathArray[i]);
  445.     if (shouldCreate && !dir.exists())
  446.       dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  447.   }
  448.   dir.followLinks = false;
  449.   return dir;
  450. }
  451.  
  452. /**
  453.  * Gets the file at the speciifed hierarchy under a Directory Service key.
  454.  * @param   key
  455.  *          The Directory Service Key to start from
  456.  * @param   pathArray
  457.  *          An array of path components to locate beneath the directory 
  458.  *          specified by |key|. The last item in this array must be the
  459.  *          leaf name of a file.
  460.  * @return  nsIFile object for the file specified. The file is NOT created
  461.  *          if it does not exist, however all required directories along 
  462.  *          the way are.
  463.  */
  464. function getFile(key, pathArray) {
  465.   var file = getDir(key, pathArray.slice(0, -1));
  466.   file.append(pathArray[pathArray.length - 1]);
  467.   return file;
  468. }
  469.  
  470. /**
  471.  * Gets the descriptor of a directory as a relative path to common base
  472.  * directories (profile, user home, app install dir, etc).
  473.  *
  474.  * @param   itemLocation
  475.  *          The nsILocalFile representing the item's directory.
  476.  * @param   installLocation the nsIInstallLocation for this item
  477.  */
  478. function getDescriptorFromFile(itemLocation, installLocation) {
  479.   var baseDir = installLocation.location;
  480.  
  481.   if (baseDir && baseDir.contains(itemLocation, true)) {
  482.     return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
  483.   }
  484.  
  485.   return "abs%" + itemLocation.persistentDescriptor;
  486. }
  487.  
  488. function getAbsoluteDescriptor(itemLocation) {
  489.   return itemLocation.persistentDescriptor;
  490. }
  491.  
  492. /**
  493.  * Initializes a Local File object based on a descriptor
  494.  * provided by "getDescriptorFromFile".
  495.  *
  496.  * @param   descriptor
  497.  *          The descriptor that locates the directory
  498.  * @param   installLocation
  499.  *          The nsIInstallLocation object for this item.
  500.  * @returns The nsILocalFile object representing the location of the item
  501.  */
  502. function getFileFromDescriptor(descriptor, installLocation) {
  503.   var location = Components.classes["@mozilla.org/file/local;1"]
  504.                            .createInstance(nsILocalFile);
  505.  
  506.   var m = descriptor.match(/^(abs|rel)\%(.*)$/);
  507.   if (!m)
  508.     throw Components.results.NS_ERROR_INVALID_ARG;
  509.  
  510.   if (m[1] == "rel") {
  511.     location.setRelativeDescriptor(installLocation.location, m[2]);
  512.   }
  513.   else {
  514.     location.persistentDescriptor = m[2];
  515.   }
  516.  
  517.   return location;
  518. }
  519.  
  520. /**
  521.  * Determines if a file is an item package - either a XPI or a JAR file.
  522.  * @param   file
  523.  *          The file to check
  524.  * @returns true if the file is an item package, false otherwise.
  525.  */
  526. function fileIsItemPackage(file) {
  527.   var fileURL = getURIFromFile(file);
  528.   if (fileURL instanceof nsIURL)
  529.     var extension = fileURL.fileExtension.toLowerCase();
  530.   return extension == "xpi" || extension == "jar";
  531. }
  532.  
  533. /** 
  534.  * Return the leaf name used by the extension system for staging an item.
  535.  * @param   id
  536.  *          The GUID of the item
  537.  * @param   type
  538.  *          The nsIUpdateItem type of the item
  539.  * @returns The leaf name of the staged file.
  540.  */
  541. function getStagedLeafName(id, type) {
  542.   if (type == nsIUpdateItem.TYPE_THEME) 
  543.     return id + ".jar";
  544.   return id + ".xpi";
  545. }
  546.  
  547. /**
  548.  * Opens a safe file output stream for writing. 
  549.  * @param   file
  550.  *          The file to write to.
  551.  * @param   modeFlags
  552.  *          (optional) File open flags. Can be undefined. 
  553.  * @returns nsIFileOutputStream to write to.
  554.  */
  555. function openSafeFileOutputStream(file, modeFlags) {
  556.   var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
  557.                       .createInstance(Components.interfaces.nsIFileOutputStream);
  558.   if (modeFlags === undefined)
  559.     modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
  560.   if (!file.exists()) 
  561.     file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  562.   fos.init(file, modeFlags, PERMS_FILE, 0);
  563.   return fos;
  564. }
  565.  
  566. /**
  567.  * Closes a safe file output stream.
  568.  * @param   stream
  569.  *          The stream to close.
  570.  */
  571. function closeSafeFileOutputStream(stream) {
  572.   if (stream instanceof Components.interfaces.nsISafeOutputStream)
  573.     stream.finish();
  574.   else
  575.     stream.close();
  576. }
  577.  
  578. /**
  579.  * Deletes a directory and its children. First it tries nsIFile::Remove(true).
  580.  * If that fails it will fall back to recursing, setting the appropriate
  581.  * permissions, and deleting the current entry. This is needed for when we have
  582.  * rights to delete a directory but there are entries that have a read-only
  583.  * attribute (e.g. a copy restore from a read-only CD, etc.)
  584.  * @param   dir
  585.  *          A nsIFile for the directory to be deleted
  586.  */
  587. function removeDirRecursive(dir) {
  588.   try {
  589.     dir.remove(true);
  590.     return;
  591.   }
  592.   catch (e) {
  593.   }
  594.  
  595.   var dirEntries = dir.directoryEntries;
  596.   while (dirEntries.hasMoreElements()) {
  597.     var entry = dirEntries.getNext().QueryInterface(Components.interfaces.nsIFile);
  598.  
  599.     if (entry.isDirectory()) {
  600.       removeDirRecursive(entry);
  601.     }
  602.     else {
  603.       entry.permissions = PERMS_FILE;
  604.       entry.remove(false);
  605.     }
  606.   }
  607.   dir.permissions = PERMS_DIRECTORY;
  608.   dir.remove(true);
  609. }
  610.  
  611. /**
  612.  * Logs a string to the error console. 
  613.  * @param   string
  614.  *          The string to write to the error console..
  615.  */  
  616. function LOG(string) {
  617.   if (gLoggingEnabled) {
  618.     dump("*** " + string + "\n");
  619.     gConsole.logStringMessage(string);
  620.   }
  621. }
  622.  
  623. /** 
  624.  * Randomize the specified file name. Used to force RDF to bypass the cache
  625.  * when loading certain types of files.
  626.  * @param   fileName 
  627.  *          A file name to randomize, e.g. install.rdf
  628.  * @returns A randomized file name, e.g. install-xyz.rdf
  629.  */
  630. function getRandomFileName(fileName) {
  631.   var extensionDelimiter = fileName.lastIndexOf(".");
  632.   var prefix = fileName.substr(0, extensionDelimiter);
  633.   var suffix = fileName.substr(extensionDelimiter);
  634.   
  635.   var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
  636.   var nameString = prefix + "-";
  637.   for (var i = 0; i < 3; ++i) {
  638.     var index = Math.round((Math.random()) * characters.length);
  639.     nameString += characters.charAt(index);
  640.   }
  641.   return nameString + "." + suffix;
  642. }
  643.  
  644. /**
  645.  * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
  646.  * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource 
  647.  * are NOT prefixed.
  648.  * @param   type
  649.  *          The nsIUpdateItem type to find a RDF URI prefix for
  650.  * @returns The RDF URI prefix.
  651.  */
  652. function getItemPrefix(type) {
  653.   if (type & nsIUpdateItem.TYPE_EXTENSION) 
  654.     return PREFIX_EXTENSION;
  655.   else if (type & nsIUpdateItem.TYPE_THEME)
  656.     return PREFIX_THEME;
  657.   return PREFIX_ITEM_URI;
  658. }
  659.  
  660. /**
  661.  * Trims a prefix from a string.
  662.  * @param   string
  663.  *          The source string
  664.  * @param   prefix
  665.  *          The prefix to remove.
  666.  * @returns The suffix (string - prefix)
  667.  */
  668. function stripPrefix(string, prefix) {
  669.   return string.substr(prefix.length);
  670. }
  671.  
  672. /**
  673.  * Gets a File URL spec for a nsIFile
  674.  * @param   file
  675.  *          The file to get a file URL spec to
  676.  * @returns The file URL spec to the file
  677.  */
  678. function getURLSpecFromFile(file) {
  679.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  680.                          .getService(Components.interfaces.nsIIOService);
  681.   var fph = ioServ.getProtocolHandler("file")
  682.                   .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  683.   return fph.getURLSpecFromFile(file);
  684. }
  685.  
  686. /**
  687.  * Constructs a URI to a spec.
  688.  * @param   spec
  689.  *          The spec to construct a URI to
  690.  * @returns The nsIURI constructed.
  691.  */
  692. function newURI(spec) {
  693.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  694.                          .getService(Components.interfaces.nsIIOService);
  695.   return ioServ.newURI(spec, null, null);
  696. }
  697.  
  698. /** 
  699.  * Constructs a File URI to a nsIFile
  700.  * @param   file
  701.  *          The file to construct a File URI to
  702.  * @returns The file URI to the file
  703.  */
  704. function getURIFromFile(file) {
  705.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  706.                          .getService(Components.interfaces.nsIIOService);
  707.   return ioServ.newFileURI(file);
  708. }
  709.  
  710. /**
  711.  * @returns Whether or not we are currently running in safe mode.
  712.  */
  713. function inSafeMode() {
  714.   return gApp.inSafeMode;
  715. }
  716.  
  717. /**
  718.  * Extract the string value from a RDF Literal or Resource
  719.  * @param   literalOrResource
  720.  *          RDF String Literal or Resource
  721.  * @returns String value of the literal or resource, or undefined if the object
  722.  *          supplied is not a RDF string literal or resource.
  723.  */
  724. function stringData(literalOrResource) {
  725.   if (literalOrResource instanceof Components.interfaces.nsIRDFLiteral)
  726.     return literalOrResource.Value;
  727.   if (literalOrResource instanceof Components.interfaces.nsIRDFResource)
  728.     return literalOrResource.Value;
  729.   return undefined;
  730. }
  731.  
  732. /**
  733.  * Extract the integer value of a RDF Literal
  734.  * @param   literal
  735.  *          nsIRDFInt literal
  736.  * @return  integer value of the literal
  737.  */
  738. function intData(literal) {
  739.   if (literal instanceof Components.interfaces.nsIRDFInt)
  740.     return literal.Value;
  741.   return undefined;
  742. }
  743.  
  744. /**
  745.  * Gets a property from an install manifest.
  746.  * @param   installManifest
  747.  *          An Install Manifest datasource to read from
  748.  * @param   property
  749.  *          The name of a proprety to read (sans EM_NS)
  750.  * @returns The literal value of the property, or undefined if the property has
  751.  *          no value.
  752.  */
  753. function getManifestProperty(installManifest, property) {
  754.   var target = installManifest.GetTarget(gInstallManifestRoot, 
  755.                                          gRDF.GetResource(EM_NS(property)), true);
  756.   var val = stringData(target);
  757.   return val === undefined ? intData(target) : val;
  758. }
  759.  
  760. /**
  761.  * Given an Install Manifest Datasource, retrieves the type of item the manifest
  762.  * describes.
  763.  * @param   installManifest 
  764.  *          The Install Manifest Datasource.
  765.  * @return  The nsIUpdateItem type of the item described by the manifest
  766.  *          returns TYPE_EXTENSION if attempts to determine the type fail.
  767.  */
  768. function getAddonTypeFromInstallManifest(installManifest) {
  769.   var target = installManifest.GetTarget(gInstallManifestRoot, 
  770.                                          gRDF.GetResource(EM_NS("type")), true);
  771.   if (target) {
  772.     var type = stringData(target);
  773.     return type === undefined ? intData(target) : parseInt(type);
  774.   }
  775.  
  776.   // Firefox 1.0 and earlier did not support addon-type annotation on the 
  777.   // Install Manifest, so we fall back to a theme-only property to 
  778.   // differentiate.
  779.   if (getManifestProperty(installManifest, "internalName") !== undefined)
  780.     return nsIUpdateItem.TYPE_THEME;
  781.  
  782.   // If no type is provided, default to "Extension"
  783.   return nsIUpdateItem.TYPE_EXTENSION;    
  784. }
  785.  
  786. /**
  787.  * Shows a message about an incompatible Extension/Theme. 
  788.  * @param   installData
  789.  *          An Install Data object from |getInstallData|
  790.  */
  791. function showIncompatibleError(installData) {
  792.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  793.   var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
  794.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  795.                                                     params, params.length);
  796.   var message;
  797.   var targetAppData = installData.currentApp;
  798.   if (!targetAppData) {
  799.     params = [installData.name, installData.version, BundleManager.appName];
  800.     message = extensionStrings.formatStringFromName("incompatibleMessageNoApp", 
  801.                                                     params, params.length);
  802.   }
  803.   else if (targetAppData.minVersion == targetAppData.maxVersion) {
  804.     // If the min target app version and the max target app version are the same, don't show
  805.     // a message like, "Foo is only compatible with Firefox versions 0.7 to 0.7", rather just
  806.     // show, "Foo is only compatible with Firefox 0.7"
  807.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  808.               installData.name, installData.version, BundleManager.appName, 
  809.               targetAppData.minVersion];
  810.     message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion", 
  811.                                                     params, params.length);
  812.   }
  813.   else {
  814.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  815.               installData.name, installData.version, BundleManager.appName, 
  816.               targetAppData.minVersion, targetAppData.maxVersion];
  817.     message = extensionStrings.formatStringFromName("incompatibleMsg", params, params.length);
  818.   }
  819.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  820.                      .getService(Components.interfaces.nsIPromptService);
  821.   ps.alert(null, title, message);
  822. }
  823.  
  824. /**
  825.  * Shows a message.
  826.  * @param   titleKey
  827.  *          String key of the title string in the Extensions localization file.
  828.  * @param   messageKey
  829.  *          String key of the message string in the Extensions localization file.
  830.  * @param   messageParams
  831.  *          Array of strings to be substituted into |messageKey|. Can be null.
  832.  */
  833. function showMessage(titleKey, titleParams, messageKey, messageParams) {
  834.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  835.   if (titleParams && titleParams.length > 0) {
  836.     var title = extensionStrings.formatStringFromName(titleKey, titleParams,
  837.                                                       titleParams.length);
  838.   }
  839.   else
  840.     title = extensionStrings.GetStringFromName(titleKey);
  841.  
  842.   if (messageParams && messageParams.length > 0) {
  843.     var message = extensionStrings.formatStringFromName(messageKey, messageParams,
  844.                                                         messageParams.length);
  845.   }
  846.   else
  847.     message = extensionStrings.GetStringFromName(messageKey);
  848.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  849.                      .getService(Components.interfaces.nsIPromptService);
  850.   ps.alert(null, title, message);
  851. }
  852.  
  853. /**
  854.  * Shows a dialog for blocklisted items.
  855.  * @param   items
  856.  *          An array of nsIUpdateItems.
  857.  * @param   fromInstall
  858.  *          Whether this is called from an install or from the blocklist
  859.  *          background check.
  860.  */
  861. function showBlocklistMessage(items, fromInstall) {
  862.   var win = null;
  863.   var params = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  864.                          .createInstance(Components.interfaces.nsIDialogParamBlock);
  865.   params.SetInt(0, (fromInstall ? 1 : 0));
  866.   params.SetInt(1, items.length);
  867.   params.SetNumberStrings(items.length * 2);
  868.   for (var i = 0; i < items.length; ++i) 
  869.     params.SetString(i, items[i].name + " " + items[i].version);
  870.  
  871.   // if this was initiated from an install try to find the appropriate manager
  872.   if (fromInstall) {
  873.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  874.                        .getService(Components.interfaces.nsIWindowMediator);
  875.     win = wm.getMostRecentWindow(nsIUpdateItem.TYPE_THEME ? "Extension:Manager-themes" :
  876.                                                             "Extension:Manager-extensions");
  877.   }
  878.   var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  879.                      .getService(Components.interfaces.nsIWindowWatcher);
  880.   ww.openWindow(win, URI_EXTENSION_LIST_DIALOG, "",
  881.                 "chrome,centerscreen,modal,dialog,titlebar", params);
  882. }
  883.  
  884. /** 
  885.  * Gets a zip reader for the file specified.
  886.  * @param   zipFile
  887.  *          A ZIP archive to open with a nsIZipReader.
  888.  * @return  A nsIZipReader for the file specified.
  889.  */
  890. function getZipReaderForFile(zipFile) {
  891.   try {
  892.     var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  893.                               .createInstance(Components.interfaces.nsIZipReader);
  894.     zipReader.init(zipFile);
  895.     zipReader.open();
  896.   }
  897.   catch (e) {
  898.     zipReader.close();
  899.     throw e;
  900.   }
  901.   return zipReader;
  902. }
  903.  
  904. /** 
  905.  * Extract a RDF file from a ZIP archive to a random location in the system
  906.  * temp directory.
  907.  * @param   zipFile
  908.  *          A ZIP archive to read from
  909.  * @param   fileName 
  910.  *          The name of the file to read from the zip. 
  911.  * @param   suppressErrors
  912.  *          Whether or not to report errors. 
  913.  * @return  The file created in the temp directory.
  914.  */
  915. function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
  916.   var file = getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
  917.   try {
  918.     var zipReader = getZipReaderForFile(zipFile);
  919.     zipReader.getEntry(fileName);
  920.     zipReader.extract(fileName, file);
  921.     zipReader.close();
  922.   }
  923.   catch (e) {
  924.     if (!suppressErrors) {
  925.       showMessage("missingFileTitle", [], "missingFileMessage", 
  926.                   [BundleManager.appName, fileName]);
  927.       throw e;
  928.     }
  929.   }
  930.   return file;
  931. }
  932.  
  933. /**
  934.  * Show a message to the user informing them they are installing an old non-EM
  935.  * style Theme, and that these are not supported.
  936.  * @param   installManifest 
  937.  *          The Old-Style Contents Manifest datasource representing the theme. 
  938.  */
  939. function showOldThemeError(contentsManifest) {
  940.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  941.   var params = [extensionStrings.GetStringFromName("theme")];
  942.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  943.                                                     params, params.length);
  944.   var appVersion = extensionStrings.GetStringFromName("incompatibleOlder");
  945.   
  946.   try {  
  947.     var ctr = getContainer(contentsManifest, 
  948.                            gRDF.GetResource("urn:mozilla:skin:root"));
  949.     var elts = ctr.GetElements();
  950.     var nameArc = gRDF.GetResource(CHROME_NS("displayName"));
  951.     while (elts.hasMoreElements()) {
  952.       var elt = elts.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  953.       themeName = stringData(contentsManifest.GetTarget(elt, nameArc, true));
  954.       if (themeName) 
  955.         break;
  956.     }
  957.   }
  958.   catch (e) {
  959.     themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  960.   }
  961.   
  962.   params = [themeName, "", BundleManager.appName, gApp.version, themeName, "", 
  963.             BundleManager.appName, appVersion];
  964.   var message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion",
  965.                                                       params, params.length);
  966.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  967.                      .getService(Components.interfaces.nsIPromptService);
  968.   ps.alert(null, title, message);
  969. }
  970.  
  971. /**
  972.  * Gets an Install Manifest datasource from a file.
  973.  * @param   file
  974.  *          The nsIFile that contains the Install Manifest RDF
  975.  * @returns The Install Manifest datasource
  976.  */
  977. function getInstallManifest(file) {
  978.   var fileURL = getURLSpecFromFile(file);
  979.   var ds = gRDF.GetDataSourceBlocking(fileURL);
  980.   var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
  981.   if (!arcs.hasMoreElements()) {
  982.     ds = null;
  983.     var uri = Components.classes["@mozilla.org/network/io-service;1"]
  984.                         .getService(Components.interfaces.nsIIOService)
  985.                         .newFileURI(file);
  986.     var url = uri.QueryInterface(nsIURL);
  987.     showMessage("malformedTitle", [], "malformedMessage", 
  988.                 [BundleManager.appName, url.fileName]);
  989.   }
  990.   return ds;
  991. }
  992.  
  993. /**
  994.  * An enumeration of items in a JS array.
  995.  * @constructor
  996.  */
  997. function ArrayEnumerator(aItems) {
  998.   this._index = 0;
  999.   if (aItems) {
  1000.     for (var i = 0; i < aItems.length; ++i) {
  1001.       if (!aItems[i])
  1002.         aItems.splice(i, 1);      
  1003.     }
  1004.   }
  1005.   this._contents = aItems;
  1006. }
  1007.  
  1008. ArrayEnumerator.prototype = {
  1009.   _index: 0,
  1010.   _contents: [],
  1011.   
  1012.   hasMoreElements: function() {
  1013.     return this._index < this._contents.length;
  1014.   },
  1015.   
  1016.   getNext: function() {
  1017.     return this._contents[this._index++];      
  1018.   }
  1019. };
  1020.  
  1021. /**
  1022.  * An enumeration of files in a JS array.
  1023.  * @param   files
  1024.  *          The files to enumerate
  1025.  * @constructor
  1026.  */
  1027. function FileEnumerator(files) {
  1028.   this._index = 0;
  1029.   if (files) {
  1030.     for (var i = 0; i < files.length; ++i) {
  1031.       if (!files[i])
  1032.         files.splice(i, 1);      
  1033.     }
  1034.   }
  1035.   this._contents = files;
  1036. }
  1037.  
  1038. FileEnumerator.prototype = {
  1039.   _index: 0,
  1040.   _contents: [],
  1041.  
  1042.   /**
  1043.    * Gets the next file in the sequence.
  1044.    */  
  1045.   get nextFile() {
  1046.     if (this._index < this._contents.length)
  1047.       return this._contents[this._index++];
  1048.     return null;
  1049.   },
  1050.   
  1051.   /**
  1052.    * Stop enumerating. Nothing to do here.
  1053.    */
  1054.   close: function() {
  1055.   },
  1056. };
  1057.  
  1058. /**
  1059.  * An object which identifies an Install Location for items, where the location
  1060.  * relationship is each item living in a directory named with its GUID under 
  1061.  * the directory used when constructing this object.
  1062.  *
  1063.  * e.g. <location>\{GUID1}
  1064.  *      <location>\{GUID2}
  1065.  *      <location>\{GUID3}
  1066.  *      ...
  1067.  *
  1068.  * @param   name
  1069.  *          The string identifier of this Install Location.
  1070.  * @param   location
  1071.  *          The directory that contains the items. 
  1072.  * @constructor
  1073.  */
  1074. function DirectoryInstallLocation(name, location, restricted, priority) {
  1075.   this._name = name;
  1076.   if (location.exists()) {
  1077.     if (!location.isDirectory())
  1078.       throw new Error("location must be a directoy!");
  1079.   }
  1080.   else {
  1081.     try {
  1082.       location.create(nsILocalFile.DIRECTORY_TYPE, 0775);
  1083.     }
  1084.     catch (e) {
  1085.       LOG("DirectoryInstallLocation: failed to create location " + 
  1086.           " directory = " + location.path + ", exception = " + e + "\n");
  1087.     }
  1088.   }
  1089.  
  1090.   this._location = location;
  1091.   this._locationToIDMap = {};
  1092.   this._restricted = restricted;
  1093.   this._priority = priority;
  1094. }
  1095. DirectoryInstallLocation.prototype = {
  1096.   _name           : "",
  1097.   _location       : null,
  1098.   _locationToIDMap: null,
  1099.   _restricted     : false,
  1100.   _priority       : 0,
  1101.   _canAccess      : null,
  1102.   
  1103.   /**
  1104.    * See nsIExtensionManager.idl
  1105.    */
  1106.   get name() {
  1107.     return this._name;
  1108.   },
  1109.   
  1110.   /**
  1111.    * Reads a directory linked to in a file.
  1112.    * @param   file
  1113.    *          The file containing the directory path
  1114.    * @returns A nsILocalFile object representing the linked directory.
  1115.    */
  1116.   _readDirectoryFromFile: function(file) {
  1117.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  1118.                         .createInstance(Components.interfaces.nsIFileInputStream);
  1119.     fis.init(file, -1, -1, false);
  1120.     var line = { value: "" };
  1121.     if (fis instanceof nsILineInputStream)
  1122.       fis.readLine(line);
  1123.     fis.close();
  1124.     if (line.value) {
  1125.       var linkedDirectory = Components.classes["@mozilla.org/file/local;1"]
  1126.                                       .createInstance(nsILocalFile);
  1127.       try {
  1128.         linkedDirectory.initWithPath(line.value);
  1129.       }
  1130.       catch (e) {
  1131.         linkedDirectory.setRelativeDescriptor(file.parent, line.value);
  1132.       }
  1133.       
  1134.       return linkedDirectory;
  1135.     }
  1136.     return null;
  1137.   },
  1138.   
  1139.   /**
  1140.    * See nsIExtensionManager.idl
  1141.    */
  1142.   get itemLocations() {
  1143.     var locations = [];
  1144.     if (!this._location.exists())
  1145.       return new FileEnumerator(locations);
  1146.     
  1147.     try {
  1148.       var entries = this._location.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1149.       while (true) {
  1150.         var entry = entries.nextFile;
  1151.         if (!entry)
  1152.           break;
  1153.         entry instanceof nsILocalFile;
  1154.         if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
  1155.           var linkedDirectory = this._readDirectoryFromFile(entry);
  1156.           if (linkedDirectory && linkedDirectory.exists() && 
  1157.               linkedDirectory.isDirectory()) {
  1158.             locations.push(linkedDirectory);
  1159.             this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
  1160.           }
  1161.         }
  1162.         else
  1163.           locations.push(entry);
  1164.       }
  1165.       entries.close();
  1166.     }
  1167.     catch (e) { 
  1168.     }
  1169.     return new FileEnumerator(locations);
  1170.   },
  1171.   
  1172.   /**
  1173.    * Retrieves the GUID for an item at the specified location.
  1174.    * @param   file
  1175.    *          The location where an item might live.
  1176.    * @returns The ID for an item that might live at the location specified.
  1177.    * 
  1178.    * N.B. This function makes no promises about whether or not this path is 
  1179.    *      actually maintained by this Install Location.
  1180.    */
  1181.   getIDForLocation: function(file) {
  1182.     var section = file.leafName;
  1183.     var filePD = file.persistentDescriptor;
  1184.     if (filePD in this._locationToIDMap) 
  1185.       section = this._locationToIDMap[filePD];
  1186.     
  1187.     if (gIDTest.test(section))
  1188.       return RegExp.$1;
  1189.     return undefined;
  1190.   },
  1191.   
  1192.   /**
  1193.    * See nsIExtensionManager.idl
  1194.    */
  1195.   get location() {
  1196.     return this._location.clone();
  1197.   },
  1198.   
  1199.   /**
  1200.    * See nsIExtensionManager.idl
  1201.    */
  1202.   get restricted() {
  1203.     return this._restricted;
  1204.   },
  1205.   
  1206.   /**
  1207.    * See nsIExtensionManager.idl
  1208.    */
  1209.   get canAccess() {
  1210.     if (this._canAccess != null)
  1211.       return this._canAccess;
  1212.  
  1213.     var testFile = this.location;
  1214.     testFile.append("Access Privileges Test");
  1215.     try {
  1216.       testFile.createUnique(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1217.       testFile.remove(false);
  1218.       this._canAccess = true;
  1219.     }
  1220.     catch (e) {
  1221.       this._canAccess = false;
  1222.     }
  1223.     return this._canAccess;
  1224.   },
  1225.   
  1226.   /**
  1227.    * See nsIExtensionManager.idl
  1228.    */
  1229.   get priority() {
  1230.     return this._priority;
  1231.   },
  1232.   
  1233.   /**
  1234.    * See nsIExtensionManager.idl
  1235.    */
  1236.   getItemLocation: function(id) {
  1237.     var itemLocation = this.location;
  1238.     itemLocation.append(id);
  1239.     if (itemLocation.exists() && !itemLocation.isDirectory())
  1240.       return this._readDirectoryFromFile(itemLocation);
  1241.     if (!itemLocation.exists() && this.canAccess)
  1242.       itemLocation.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1243.     return itemLocation;
  1244.   },
  1245.   
  1246.   /**
  1247.    * See nsIExtensionManager.idl
  1248.    */
  1249.   itemIsManagedIndependently: function(id) {
  1250.     var itemLocation = this.location;
  1251.     itemLocation.append(id);
  1252.     return itemLocation.exists() && !itemLocation.isDirectory();      
  1253.   },
  1254.   
  1255.   /**
  1256.    * See nsIExtensionManager.idl
  1257.    */
  1258.   getItemFile: function(id, filePath) {
  1259.     var itemLocation = this.getItemLocation(id).clone();
  1260.     var parts = filePath.split("/");
  1261.     for (var i = 0; i < parts.length; ++i)
  1262.       itemLocation.append(parts[i]);
  1263.     return itemLocation;
  1264.   },
  1265.  
  1266.   /**
  1267.    * Stages the specified file for later.
  1268.    * @param   file
  1269.    *          The file to stage
  1270.    * @param   id
  1271.    *          The GUID of the item the file represents
  1272.    */
  1273.   stageFile: function(file, id) {
  1274.     var stagedFile = this.location;
  1275.     stagedFile.append(DIR_STAGE);
  1276.     stagedFile.append(id);
  1277.     stagedFile.append(file.leafName);
  1278.  
  1279.     // When an incompatible update is successful the file is already staged
  1280.     if (stagedFile.equals(file))
  1281.       return stagedFile;
  1282.  
  1283.     if (stagedFile.exists()) 
  1284.       stagedFile.remove(false);
  1285.       
  1286.     file.copyTo(stagedFile.parent, stagedFile.leafName);
  1287.     
  1288.     // If the file has incorrect permissions set, correct them now.
  1289.     if (!stagedFile.isWritable())
  1290.       stagedFile.permissions = PERMS_FILE;
  1291.     
  1292.     return stagedFile;
  1293.   },
  1294.   
  1295.   /**
  1296.    * Returns the most recently staged package (e.g. the last XPI or JAR in a
  1297.    * directory) for an item and removes items that do not qualify.
  1298.    * @param   id
  1299.    *          The ID of the staged package
  1300.    * @returns an nsIFile if the package exists otherwise null.
  1301.    */
  1302.   getStageFile: function(id) {
  1303.     var stageFile = null;
  1304.     var stageDir = this.location;
  1305.     stageDir.append(DIR_STAGE);
  1306.     stageDir.append(id);
  1307.     if (!stageDir.exists() || !stageDir.isDirectory())
  1308.       return null;
  1309.     try {
  1310.       var entries = stageDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1311.       while (entries.hasMoreElements()) {
  1312.         var file = entries.nextFile;
  1313.         if (!(file instanceof nsILocalFile))
  1314.           continue;
  1315.         if (file.isDirectory())
  1316.           removeDirRecursive(file);
  1317.         else if (fileIsItemPackage(file)) {
  1318.           if (stageFile)
  1319.             stageFile.remove(false);
  1320.           stageFile = file;
  1321.         }
  1322.         else
  1323.           file.remove(false);
  1324.       }
  1325.     }
  1326.     catch (e) {
  1327.     }
  1328.     if (entries instanceof nsIDirectoryEnumerator)
  1329.       entries.close();
  1330.     return stageFile;
  1331.   },
  1332.   
  1333.   /**
  1334.    * Removes a file from the stage. This cleans up the stage if there is nothing
  1335.    * else left after the remove operation.
  1336.    * @param   file
  1337.    *          The file to remove.
  1338.    */
  1339.   removeFile: function(file) {
  1340.     if (file.exists())
  1341.       file.remove(false);
  1342.     var parent = file.parent;
  1343.     var entries = parent.directoryEntries;    
  1344.     try {
  1345.       // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  1346.       // it has been removed will cause a crash on Mac OS X - bug 292823
  1347.       while (parent && !parent.equals(this.location) &&
  1348.             !entries.hasMoreElements()) {
  1349.         parent.remove(false);
  1350.         parent = parent.parent;
  1351.         entries = parent.directoryEntries;
  1352.       }
  1353.       if (entries instanceof nsIDirectoryEnumerator)
  1354.         entries.close();
  1355.     }
  1356.     catch (e) {
  1357.       LOG("DirectoryInstallLocation::removeFile: failed to remove staged " + 
  1358.           " directory = " + parent.path + ", exception = " + e + "\n");
  1359.     }
  1360.   },
  1361.   
  1362.   /**
  1363.    * See nsISupports.idl
  1364.    */
  1365.   QueryInterface: function (iid) {
  1366.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1367.         !iid.equals(Components.interfaces.nsISupports))
  1368.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1369.     return this;
  1370.   }
  1371. };
  1372.  
  1373. //@line 1295 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1374.  
  1375. const nsIWindowsRegKey = Components.interfaces.nsIWindowsRegKey;
  1376.  
  1377. /**
  1378.  * An object that identifies the location of installed items based on entries
  1379.  * in the Windows registry.  For each application a subkey is defined that
  1380.  * contains a set of values, where the name of each value is a GUID and the
  1381.  * contents of the value is a filesystem path identifying a directory
  1382.  * containing an installed item.
  1383.  *
  1384.  * @param   name
  1385.  *          The string identifier of this Install Location.
  1386.  * @param   rootKey
  1387.  *          The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
  1388.  * @param   restricted
  1389.  *          Indicates that the location may be restricted (e.g., this is
  1390.  *          usually true of a system level install location).
  1391.  * @param   priority
  1392.  *          The priority of this install location.
  1393.  * @constructor
  1394.  */
  1395. function WinRegInstallLocation(name, rootKey, restricted, priority) {
  1396.   this._name = name;
  1397.   this._rootKey = rootKey;
  1398.   this._restricted = restricted;
  1399.   this._priority = priority;
  1400.   this._IDToDirMap = {};
  1401.   this._DirToIDMap = {};
  1402.  
  1403.   // Reading the registry may throw an exception, and that's ok.  In error
  1404.   // cases, we just leave ourselves in the empty state.
  1405.   try {
  1406.     var path = this._appKeyPath + "\\Extensions";
  1407.     var key = Components.classes["@mozilla.org/windows-registry-key;1"]
  1408.                         .createInstance(nsIWindowsRegKey);
  1409.     key.open(this._rootKey, path, nsIWindowsRegKey.ACCESS_READ);
  1410.     this._readAddons(key);
  1411.   } catch (e) {
  1412.     if (key)
  1413.       key.close();
  1414.   }
  1415. }
  1416. WinRegInstallLocation.prototype = {
  1417.   _name       : "",
  1418.   _rootKey    : null,
  1419.   _restricted : false,
  1420.   _priority   : 0,
  1421.   _IDToDirMap : null,  // mapping from ID to directory object
  1422.   _DirToIDMap : null,  // mapping from directory path to ID
  1423.   
  1424.   /**
  1425.    * Retrieves the path of this Application's data key in the registry.
  1426.    */
  1427.   get _appKeyPath() {
  1428.     var appVendor = gApp.vendor;
  1429.     var appName = gApp.name;
  1430.  
  1431. //@line 1357 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1432.   
  1433.     // XULRunner-based apps may intentionally not specify a vendor:
  1434.     if (appVendor != "")
  1435.       appVendor += "\\";
  1436.  
  1437.     return "SOFTWARE\\" + appVendor + appName;
  1438.   },
  1439.  
  1440.   /**
  1441.    * Read the registry and build a mapping between GUID and directory for each
  1442.    * installed item.
  1443.    * @param   key
  1444.    *          The key that contains the GUID->path pairs
  1445.    */
  1446.   _readAddons: function(key) {
  1447.     var count = key.valueCount; 
  1448.     for (var i = 0; i < count; ++i) {
  1449.       var id = key.getValueName(i);
  1450.  
  1451.       var dir = Components.classes["@mozilla.org/file/local;1"]
  1452.                           .createInstance(nsILocalFile);
  1453.       dir.initWithPath(key.readStringValue(id));
  1454.  
  1455.       if (dir.exists() && dir.isDirectory()) {
  1456.         this._IDToDirMap[id] = dir;
  1457.         this._DirToIDMap[dir.path] = id;
  1458.       }
  1459.     }
  1460.   },
  1461.  
  1462.   get name() {
  1463.     return this._name;
  1464.   },
  1465.  
  1466.   get itemLocations() {
  1467.     var locations = [];
  1468.     for (var id in this._IDToDirMap) {
  1469.       locations.push(this._IDToDirMap[id]);
  1470.     }
  1471.     return new FileEnumerator(locations);
  1472.   },
  1473.  
  1474.   get location() {
  1475.     return null;
  1476.   },
  1477.  
  1478.   get restricted() {
  1479.     return this._restricted;
  1480.   },
  1481.  
  1482.   // you should never be able to write to this location
  1483.   get canAccess() {
  1484.     return false;
  1485.   },
  1486.  
  1487.   get priority() {
  1488.     return this._priority;
  1489.   },
  1490.  
  1491.   getItemLocation: function(id) {
  1492.     return this._IDToDirMap[id];
  1493.   },
  1494.  
  1495.   getIDForLocation: function(dir) {
  1496.     return this._DirToIDMap[dir.path];
  1497.   },
  1498.  
  1499.   getItemFile: function(id, filePath) {
  1500.     var itemLocation = this.getItemLocation(id).clone();
  1501.     var parts = filePath.split("/");
  1502.     for (var i = 0; i < parts.length; ++i)
  1503.       itemLocation.append(parts[i]);
  1504.     return itemLocation;
  1505.   },
  1506.  
  1507.   itemIsManagedIndependently: function(id) {
  1508.     return true;
  1509.   },
  1510.  
  1511.   QueryInterface: function(iid) {
  1512.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1513.         !iid.equals(Components.interfaces.nsISupports))
  1514.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1515.     return this;
  1516.   }
  1517. };
  1518.  
  1519. //@line 1445 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1520.  
  1521. /**
  1522.  * An object which handles the installation of an Extension.
  1523.  * @constructor
  1524.  */
  1525. function Installer(ds, id, installLocation, type) {
  1526.   this._ds = ds;
  1527.   this._id = id;
  1528.   this._type = type;
  1529.   this._installLocation = installLocation;
  1530. }
  1531. Installer.prototype = {
  1532.   // Item metadata
  1533.   _id: null,
  1534.   _ds: null,
  1535.   _installLocation: null,
  1536.   _metadataDS: null,
  1537.   
  1538.   /**
  1539.    * Gets the Install Manifest datasource we are installing from.
  1540.    */
  1541.   get metadataDS() {
  1542.     if (!this._metadataDS) {
  1543.       var metadataFile = this._installLocation
  1544.                              .getItemFile(this._id, FILE_INSTALL_MANIFEST);
  1545.       if (!metadataFile.exists()) 
  1546.         return null;
  1547.       this._metadataDS = getInstallManifest(metadataFile);
  1548.       if (!this._metadataDS) {
  1549.         LOG("Installer::install: metadata datasource for extension " + 
  1550.             this._id + " at " + metadataFile.path + " could not be loaded. " + 
  1551.             " Installation will not proceed.");
  1552.       }
  1553.     }
  1554.     return this._metadataDS;
  1555.   },
  1556.   
  1557.   /**
  1558.    * Installs the Extension
  1559.    * @param   file
  1560.    *          A XPI/JAR file to install from. If this is null or does not exist,
  1561.    *          the item is assumed to be an expanded directory, located at the GUID
  1562.    *          key in the supplied Install Location.
  1563.    */
  1564.   installFromFile: function(file) {
  1565.     // Move files from the staging dir into the extension's final home.
  1566.     if (file && file.exists()) {
  1567.       this._installExtensionFiles(file);
  1568.     }
  1569.  
  1570.     if (!this.metadataDS)
  1571.       return;
  1572.  
  1573.     // Upgrade old-style contents.rdf Chrome Manifests if necessary.
  1574.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1575.       this.upgradeThemeChrome();
  1576.     else
  1577.       this.upgradeExtensionChrome();
  1578.  
  1579.     // Add metadata for the extension to the global extension metadata set
  1580.     this._ds.addItemMetadata(this._id, this.metadataDS, this._installLocation);
  1581.   },
  1582.   
  1583.   /**
  1584.    * Safely extract the Extension's files into the target folder.
  1585.    * @param   file
  1586.    *          The XPI/JAR file to install from.
  1587.    */
  1588.   _installExtensionFiles: function(file) {
  1589.     var installer = this;
  1590.     /**
  1591.       * Callback for |safeInstallOperation| that performs file level installation
  1592.       * steps for an Extension.
  1593.       * @param   extensionID
  1594.       *          The GUID of the Extension being installed.
  1595.       * @param   installLocation 
  1596.       *          The Install Location where the Extension is being installed.
  1597.       * @param   xpiFile
  1598.       *          The source XPI file that contains the Extension.
  1599.       */
  1600.     function extractExtensionFiles(extensionID, installLocation, xpiFile) {
  1601.       // Create a logger to log install operations for uninstall. This must be 
  1602.       // created in the |safeInstallOperation| callback, since it creates a file
  1603.       // in the target directory. If we do this outside of the callback, we may
  1604.       // be clobbering a file we should not be.
  1605.       var zipReader = getZipReaderForFile(xpiFile);
  1606.       
  1607.       // create directories first
  1608.       var entries = zipReader.findEntries("*/");
  1609.       while (entries.hasMoreElements()) {
  1610.         var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1611.         var target = installLocation.getItemFile(extensionID, entry.name);
  1612.         if (!target.exists()) {
  1613.           try {
  1614.             target.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1615.           }
  1616.           catch (e) {
  1617.             LOG("extractExtensionsFiles: failed to create target directory for extraction " + 
  1618.                 " file = " + target.path + ", exception = " + e + "\n");
  1619.           }
  1620.         }
  1621.       }
  1622.  
  1623.       entries = zipReader.findEntries("*");
  1624.       while (entries.hasMoreElements()) {
  1625.         entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1626.         target = installLocation.getItemFile(extensionID, entry.name);
  1627.         if (target.exists())
  1628.           continue;
  1629.  
  1630.         try {
  1631.           target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1632.         }
  1633.         catch (e) {
  1634.           LOG("extractExtensionsFiles: failed to create target file for extraction " + 
  1635.               " file = " + target.path + ", exception = " + e + "\n");
  1636.         }
  1637.         zipReader.extract(entry.name, target);
  1638.       }
  1639.       zipReader.close();
  1640.     }
  1641.  
  1642.     var installer = this;
  1643.     /**
  1644.       * Callback for |safeInstallOperation| that performs file level installation
  1645.       * steps for a Theme.
  1646.       * @param   id
  1647.       *          The GUID of the Theme being installed.
  1648.       * @param   installLocation 
  1649.       *          The Install Location where the Theme is being installed.
  1650.       * @param   jarFile
  1651.       *          The source JAR file that contains the Theme.
  1652.       */
  1653.     function extractThemeFiles(id, installLocation, jarFile) {
  1654.       var themeDirectory = installLocation.getItemLocation(id);
  1655.       var zipReader = getZipReaderForFile(jarFile);
  1656.  
  1657.       // The only critical file is the install.rdf and we would not have
  1658.       // gotten this far without one.
  1659.       var rootFiles = [FILE_INSTALL_MANIFEST, FILE_CHROME_MANIFEST,
  1660.                        "preview.png", "icon.png"];
  1661.       for (var i = 0; i < rootFiles.length; ++i) {
  1662.         try {
  1663.           var entry = zipReader.getEntry(rootFiles[i]);
  1664.           var target = installLocation.getItemFile(id, rootFiles[i]);
  1665.           zipReader.extract(rootFiles[i], target);
  1666.         }
  1667.         catch (e) {
  1668.         }
  1669.       }
  1670.  
  1671.       var manifestFile = installLocation.getItemFile(id, FILE_CHROME_MANIFEST);
  1672.       // new theme structure requires a chrome.manifest file
  1673.       if (manifestFile.exists()) {
  1674.         var entries = zipReader.findEntries(DIR_CHROME + "/*");
  1675.         while (entries.hasMoreElements()) {
  1676.           entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1677.           if (entry.name.substr(entry.name.length - 1, 1) == "/")
  1678.             continue;
  1679.           target = installLocation.getItemFile(id, entry.name);
  1680.           try {
  1681.             target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1682.           }
  1683.           catch (e) {
  1684.             LOG("extractThemeFiles: failed to create target file for extraction " + 
  1685.                 " file = " + target.path + ", exception = " + e + "\n");
  1686.           }
  1687.           zipReader.extract(entry.name, target);
  1688.         }
  1689.         zipReader.close();
  1690.       }
  1691.       else { // old theme structure requires only an install.rdf
  1692.         try {
  1693.           var entry = zipReader.getEntry(FILE_CONTENTS_MANIFEST);
  1694.           var contentsManifestFile = installLocation.getItemFile(id, FILE_CONTENTS_MANIFEST);
  1695.           contentsManifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1696.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  1697.         }
  1698.         catch (e) {
  1699.           zipReader.close();
  1700.           LOG("extractThemeFiles: failed to extract contents.rdf: " + target.path);
  1701.           throw e; // let the safe-op clean up
  1702.         }
  1703.         zipReader.close();
  1704.         var chromeDir = installLocation.getItemFile(id, DIR_CHROME);
  1705.         try {
  1706.           jarFile.copyTo(chromeDir, jarFile.leafName);
  1707.         }
  1708.         catch (e) {
  1709.           LOG("extractThemeFiles: failed to copy theme JAR file to: " + chromeDir.path);
  1710.           throw e; // let the safe-op clean up
  1711.         }
  1712.  
  1713.         if (!installer.metadataDS && installer._type == nsIUpdateItem.TYPE_THEME) {
  1714.           if (contentsManifestFile && contentsManifestFile.exists()) {
  1715.             var contentsManifest = gRDF.GetDataSourceBlocking(getURLSpecFromFile(contentsManifestFile));
  1716.             showOldThemeError(contentsManifest);
  1717.           }
  1718.           LOG("Theme JAR file: " + jarFile.leafName + " contains an Old-Style " + 
  1719.               "Theme that is not compatible with this version of the software.");
  1720.           throw new Error("Old Theme"); // let the safe-op clean up
  1721.         }
  1722.       }
  1723.     }
  1724.  
  1725.     var callback = extractExtensionFiles;
  1726.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1727.       callback = extractThemeFiles;
  1728.     safeInstallOperation(this._id, this._installLocation,
  1729.                           { callback: callback, data: file });
  1730.   },
  1731.   
  1732.   /** 
  1733.    * Upgrade contents.rdf Chrome Manifests used by this Theme to the new 
  1734.    * chrome.manifest format if necessary.
  1735.    */
  1736.   upgradeThemeChrome: function() {
  1737.     // Use the Chrome Registry API to install the theme there
  1738.     var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1739.                        .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1740.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1741.     if (manifestFile.exists() ||
  1742.         this._id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  1743.       return;
  1744.  
  1745.     try {
  1746.       // creates a chrome manifest for themes
  1747.       var manifestURI = getURIFromFile(manifestFile);
  1748.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1749.       // We're relying on the fact that there is only one JAR file
  1750.       // in the "chrome" directory. This is a hack, but it works.
  1751.       var entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1752.       var jarFile = entries.nextFile;
  1753.       if (jarFile) {
  1754.         var jarFileURI = getURIFromFile(jarFile);
  1755.         var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  1756.         var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1757.         var contentsFileURI = getURIFromFile(contentsFile.parent);
  1758.  
  1759.         cr.processContentsManifest(contentsFileURI, manifestURI, contentsURI, false, true);
  1760.       }
  1761.       entries.close();
  1762.       contentsFile.remove(false);
  1763.     }
  1764.     catch (e) {
  1765.       // Failed to register chrome, for any number of reasons - non-existent 
  1766.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1767.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1768.       // extension is uninstalled properly during the subsequent uninstall 
  1769.       // pass in |ExtensionManager::_finalizeOperations|
  1770.       LOG("upgradeThemeChrome: failed for theme " + this._id + " - why " + 
  1771.           "not convert to the new chrome.manifest format while you're at it? " + 
  1772.           "Failure exception: " + e);
  1773.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1774.                   [BundleManager.appName]);
  1775.  
  1776.       var stageFile = this._installLocation.getStageFile(this._id);
  1777.       if (stageFile)
  1778.         this._installLocation.removeFile(stageFile);
  1779.  
  1780.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1781.       StartupCache.write();
  1782.     }
  1783.   },
  1784.  
  1785.   /** 
  1786.    * Upgrade contents.rdf Chrome Manifests used by this Extension to the new 
  1787.    * chrome.manifest format if necessary.
  1788.    */
  1789.   upgradeExtensionChrome: function() {
  1790.     // If the extension is aware of the new flat chrome manifests and has 
  1791.     // included one, just use it instead of generating one from the
  1792.     // install.rdf/contents.rdf data.
  1793.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1794.     if (manifestFile.exists())
  1795.       return;
  1796.  
  1797.     try {
  1798.       // Enumerate the metadata datasource files collection and register chrome
  1799.       // for each file, calling _registerChrome for each.
  1800.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1801.       
  1802.       if (!manifestFile.parent.exists())
  1803.         return;
  1804.  
  1805.       // Even if an extension doesn't have any chrome, we generate an empty
  1806.       // manifest file so that we don't try to upgrade from the "old-style"
  1807.       // chrome manifests at every startup.
  1808.       manifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1809.  
  1810.       var manifestURI = getURIFromFile(manifestFile);
  1811.       var files = this.metadataDS.GetTargets(gInstallManifestRoot, EM_R("file"), true);
  1812.       while (files.hasMoreElements()) {
  1813.         var file = files.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  1814.         var chromeFile = chromeDir.clone();
  1815.         var fileName = file.Value.substr("urn:mozilla:extension:file:".length, file.Value.length);
  1816.         chromeFile.append(fileName);
  1817.  
  1818.         var fileURLSpec = getURLSpecFromFile(chromeFile);
  1819.         if (!chromeFile.isDirectory()) {
  1820.           var zipReader = getZipReaderForFile(chromeFile);
  1821.           fileURLSpec = "jar:" + fileURLSpec + "!/";
  1822.           var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1823.           contentsFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1824.         }
  1825.  
  1826.         var providers = [EM_R("package"), EM_R("skin"), EM_R("locale")];
  1827.         for (var i = 0; i < providers.length; ++i) {
  1828.           var items = this.metadataDS.GetTargets(file, providers[i], true);
  1829.           while (items.hasMoreElements()) {
  1830.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFLiteral);
  1831.             var fileURI = newURI(fileURLSpec + item.Value);
  1832.             // Extract the contents.rdf files instead of opening them inside of
  1833.             // the jar. This prevents the jar from being cached by the zip
  1834.             // reader which will keep the jar in use and prevent deletion.
  1835.             if (zipReader) {
  1836.               zipReader.extract(item.Value + FILE_CONTENTS_MANIFEST, contentsFile);
  1837.               var contentsFileURI = getURIFromFile(contentsFile.parent);
  1838.             }
  1839.             else
  1840.               contentsFileURI = fileURI;
  1841.  
  1842.             var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1843.                                .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1844.             cr.processContentsManifest(contentsFileURI, manifestURI, fileURI, true, false);
  1845.           }
  1846.         }
  1847.         if (zipReader) {
  1848.           zipReader.close();
  1849.           zipReader = null;
  1850.           contentsFile.remove(false);
  1851.         }
  1852.       }
  1853.     }
  1854.     catch (e) {
  1855.       // Failed to register chrome, for any number of reasons - non-existent 
  1856.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1857.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1858.       // extension is uninstalled properly during the subsequent uninstall 
  1859.       // pass in |ExtensionManager::_finalizeOperations|
  1860.       LOG("upgradeExtensionChrome: failed for extension " + this._id + " - why " + 
  1861.           "not convert to the new chrome.manifest format while you're at it? " + 
  1862.           "Failure exception: " + e);
  1863.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1864.                   [BundleManager.appName]);
  1865.  
  1866.       var stageFile = this._installLocation.getStageFile(this._id);
  1867.       if (stageFile)
  1868.         this._installLocation.removeFile(stageFile);
  1869.  
  1870.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1871.       StartupCache.write();
  1872.     }
  1873.   }  
  1874. };
  1875.  
  1876. /**
  1877.  * Safely attempt to perform a caller-defined install operation for a given
  1878.  * item ID. Using aggressive success-safety checks, this function will attempt
  1879.  * to move an existing location for an item aside and then allow installation
  1880.  * into the appropriate folder. If any operation fails the installation will 
  1881.  * abort and roll back from the moved-aside old version.
  1882.  * @param   itemID
  1883.  *          The GUID of the item to perform the operation on.
  1884.  * @param   installLocation
  1885.  *          The Install Location where the item is installed.
  1886.  * @param   installCallback
  1887.  *          A caller supplied JS object with the following properties:
  1888.  *          "data"      A data parameter to be passed to the callback.
  1889.  *          "callback"  A function to perform the install operation. This
  1890.  *                      function is passed three parameters:
  1891.  *                      1. The GUID of the item being operated on.
  1892.  *                      2. The Install Location where the item is installed.
  1893.  *                      3. The "data" parameter on the installCallback object.
  1894.  */
  1895. function safeInstallOperation(itemID, installLocation, installCallback) {
  1896.   var movedFiles = [];
  1897.   
  1898.   /**
  1899.    * Reverts a deep move by moving backed up files back to their original
  1900.    * location.
  1901.    */
  1902.   function rollbackMove()
  1903.   {
  1904.     for (var i = 0; i < movedFiles.length; ++i) {
  1905.       var oldFile = movedFiles[i].oldFile;
  1906.       var newFile = movedFiles[i].newFile;
  1907.       try {
  1908.         newFile.moveTo(oldFile.parent, newFile.leafName);
  1909.       }
  1910.       catch (e) {
  1911.         LOG("safeInstallOperation: failed to roll back files after an install " + 
  1912.             "operation failed. Failed to roll back: " + newFile.path + " to: " + 
  1913.             oldFile.path + " ... aborting installation.");
  1914.         throw e;
  1915.       }
  1916.     }
  1917.   }
  1918.   
  1919.   /**
  1920.    * Moves a file to a new folder.
  1921.    * @param   file
  1922.    *          The file to move
  1923.    * @param   destination
  1924.    *          The target folder
  1925.    */
  1926.   function moveFile(file, destination) {
  1927.     try {
  1928.       var oldFile = file.clone();
  1929.       file.moveTo(destination, file.leafName);
  1930.       movedFiles.push({ oldFile: oldFile, newFile: file });
  1931.     }
  1932.     catch (e) {
  1933.       LOG("safeInstallOperation: failed to back up file: " + file.path + " to: " + 
  1934.           destination.path + " ... rolling back file moves and aborting " + 
  1935.           "installation.");
  1936.       rollbackMove();
  1937.       throw e;
  1938.     }
  1939.   }
  1940.   
  1941.   /**
  1942.    * Moves a directory to a new location. If any part of the move fails,
  1943.    * files already moved will be rolled back.
  1944.    * @param   sourceDir
  1945.    *          The directory to move
  1946.    * @param   targetDir
  1947.    *          The destination directory
  1948.    * @param   currentDir
  1949.    *          The current directory (a subdirectory of |sourceDir| or 
  1950.    *          |sourceDir| itself) we are moving files from.
  1951.    */
  1952.   function moveDirectory(sourceDir, targetDir, currentDir) {
  1953.     var entries = currentDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1954.     while (true) {
  1955.       var entry = entries.nextFile;
  1956.       if (!entry)
  1957.         break;
  1958.       if (entry.isDirectory())
  1959.         moveDirectory(sourceDir, targetDir, entry);
  1960.       else if (entry instanceof nsILocalFile) {
  1961.         var rd = entry.getRelativeDescriptor(sourceDir);
  1962.         var destination = targetDir.clone().QueryInterface(nsILocalFile);
  1963.         destination.setRelativeDescriptor(targetDir, rd);
  1964.         moveFile(entry, destination.parent);
  1965.       }
  1966.     }
  1967.     entries.close();
  1968.   }
  1969.   
  1970.   /**
  1971.    * Removes the temporary backup directory where we stored files. 
  1972.    * @param   directory
  1973.    *          The backup directory to remove
  1974.    */
  1975.   function cleanUpTrash(directory) {
  1976.     try {
  1977.       // Us-generated. Safe.
  1978.       if (directory && directory.exists())
  1979.         removeDirRecursive(directory);
  1980.     }
  1981.     catch (e) {
  1982.       LOG("safeInstallOperation: failed to clean up the temporary backup of the " + 
  1983.           "older version: " + itemLocationTrash.path);
  1984.       // This is a non-fatal error. Annoying, but non-fatal. 
  1985.     }
  1986.   }
  1987.   
  1988.   if (!installLocation.itemIsManagedIndependently(itemID)) {
  1989.     var itemLocation = installLocation.getItemLocation(itemID);
  1990.     if (itemLocation.exists()) {
  1991.       var trashDirName = itemID + "-trash";
  1992.       var itemLocationTrash = itemLocation.parent.clone();
  1993.       itemLocationTrash.append(trashDirName);
  1994.       if (itemLocationTrash.exists()) {
  1995.         // We can remove recursively here since this is a folder we created, not
  1996.         // one the user specified. If this fails, it'll throw, and the caller 
  1997.         // should stop installation.
  1998.         try {
  1999.           removeDirRecursive(itemLocationTrash);
  2000.         }
  2001.         catch (e) {
  2002.           LOG("safeFileOperation: failed to remove existing trash directory " + 
  2003.               itemLocationTrash.path + " ... aborting installation.");
  2004.           throw e;
  2005.         }
  2006.       }
  2007.       
  2008.       // Move the directory that contains the existing version of the item aside, 
  2009.       // into {GUID}-trash. This will throw if there's a failure and the install
  2010.       // will abort.
  2011.       moveDirectory(itemLocation, itemLocationTrash, itemLocation);
  2012.       
  2013.       // Clean up the original location, if necessary. Again, this is a path we 
  2014.       // generated, so it is safe to recursively delete.
  2015.       try {
  2016.         removeDirRecursive(itemLocation);
  2017.       }
  2018.       catch (e) {
  2019.         LOG("safeInstallOperation: failed to clean up item location after its contents " + 
  2020.             "were properly backed up. Failed to clean up: " + itemLocation.path + 
  2021.             " ... rolling back file moves and aborting installation.");
  2022.         rollbackMove();
  2023.         cleanUpTrash(itemLocationTrash);
  2024.         throw e;
  2025.       }
  2026.     }
  2027.   }
  2028.   else if (installLocation.name == KEY_APP_PROFILE ||
  2029.            installLocation.name == KEY_APP_GLOBAL) {
  2030.     // Check for a pointer file and move it aside if it exists
  2031.     var pointerFile = installLocation.location.clone();
  2032.     pointerFile.append(itemID);
  2033.     if (pointerFile.exists() && !pointerFile.isDirectory()) {
  2034.       var trashFileName = itemID + "-trash";
  2035.       var itemLocationTrash = installLocation.location.clone();
  2036.       itemLocationTrash.append(trashFileName);
  2037.       if (itemLocationTrash.exists()) {
  2038.         // We can remove recursively here since this is a folder we created, not
  2039.         // one the user specified. If this fails, it'll throw, and the caller 
  2040.         // should stop installation.
  2041.         try {
  2042.           removeDirRecursive(itemLocationTrash);
  2043.         }
  2044.         catch (e) {
  2045.           LOG("safeFileOperation: failed to remove existing trash directory " + 
  2046.               itemLocationTrash.path + " ... aborting installation.");
  2047.           throw e;
  2048.         }
  2049.       }
  2050.       itemLocationTrash.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  2051.       // Move the pointer file to the trash.
  2052.       moveFile(pointerFile, itemLocationTrash);
  2053.     }
  2054.   }
  2055.       
  2056.   // Now tell the client to do their stuff.
  2057.   try {
  2058.     installCallback.callback(itemID, installLocation, installCallback.data);
  2059.   }
  2060.   catch (e) {
  2061.     // This means the install operation failed. Remove everything and roll back.
  2062.     LOG("safeInstallOperation: install operation (caller-supplied callback) failed, " + 
  2063.         "rolling back file moves and aborting installation.");
  2064.     try {
  2065.       // Us-generated. Safe.
  2066.       removeDirRecursive(itemLocation);
  2067.     }
  2068.     catch (e) {
  2069.       LOG("safeInstallOperation: failed to remove the folder we failed to install " + 
  2070.           "an item into: " + itemLocation.path + " -- There is not much to suggest " + 
  2071.           "here... maybe restart and try again?");
  2072.       cleanUpTrash(itemLocationTrash);
  2073.       throw e;
  2074.     }
  2075.     rollbackMove();
  2076.     cleanUpTrash(itemLocationTrash);
  2077.     throw e;        
  2078.   }
  2079.   
  2080.   // Now, and only now - after everything else has succeeded (against all odds!) 
  2081.   // remove the {GUID}-trash directory where we stashed the old version of the 
  2082.   // item.
  2083.   cleanUpTrash(itemLocationTrash);
  2084. }
  2085.  
  2086. /**
  2087.  * Manages the list of pending operations.
  2088.  */
  2089. var PendingOperations = {
  2090.   _ops: { },
  2091.  
  2092.   /**
  2093.    * Adds an entry to the Pending Operations List
  2094.    * @param   opType
  2095.    *          The type of Operation to be performed
  2096.    * @param   entry
  2097.    *          A JS Object representing the item to be operated on:
  2098.    *          "locationKey"   The name of the Install Location where the item
  2099.    *                          is installed.
  2100.    *          "id"            The GUID of the item.
  2101.    */
  2102.   addItem: function(opType, entry) {
  2103.     if (opType == OP_NONE)
  2104.       this.clearOpsForItem(entry.id);
  2105.     else {
  2106.       if (!(opType in this._ops))
  2107.         this._ops[opType] = { };
  2108.       this._ops[opType][entry.id] = entry.locationKey;
  2109.     }
  2110.   },
  2111.   
  2112.   /**
  2113.    * Removes a Pending Operation from the list
  2114.    * @param   opType
  2115.    *          The type of Operation being removed
  2116.    * @param   id
  2117.    *          The GUID of the item to remove the entry for
  2118.    */
  2119.   clearItem: function(opType, id) {
  2120.     if (opType in this._ops && id in this._ops[opType])
  2121.       delete this._ops[opType][id];
  2122.   },
  2123.   
  2124.   /**
  2125.    * Removes all Pending Operation for an item
  2126.    * @param   id
  2127.    *          The ID of the item to remove the entries for
  2128.    */
  2129.   clearOpsForItem: function(id) {
  2130.     for (var opType in this._ops) {
  2131.       if (id in this._ops[opType])
  2132.         delete this._ops[opType][id];
  2133.     }
  2134.   },
  2135.  
  2136.   /**
  2137.    * Remove all Pending Operations of a certain type
  2138.    * @param   opType
  2139.    *          The type of Operation to remove all entries for
  2140.    */
  2141.   clearItems: function(opType) {
  2142.     if (opType in this._ops)
  2143.       delete this._ops[opType];
  2144.   },
  2145.   
  2146.   /**
  2147.    * Get an array of operations of a certain type
  2148.    * @param   opType
  2149.    *          The type of Operation to return a list of
  2150.    */
  2151.   getOperations: function(opType) {
  2152.     if (!(opType in this._ops))
  2153.       return [];
  2154.     var ops = [];
  2155.     for (var id in this._ops[opType])
  2156.       ops.push( {id: id, locationKey: this._ops[opType][id] } );
  2157.     return ops;
  2158.   },
  2159.   
  2160.   /**
  2161.    * The total number of Pending Operations, for all types.
  2162.    */
  2163.   get size() {
  2164.     var size = 0;
  2165.     for (var opType in this._ops) {
  2166.       for (var id in this._ops[opType])
  2167.         ++size;
  2168.     }
  2169.     return size;
  2170.   }
  2171. };
  2172.  
  2173. /**
  2174.  * Manages registered Install Locations
  2175.  */
  2176. var InstallLocations = { 
  2177.   _locations: { },
  2178.  
  2179.   /**
  2180.    * A nsISimpleEnumerator of all available Install Locations.
  2181.    */
  2182.   get enumeration() {
  2183.     var installLocations = [];
  2184.     for (var key in this._locations) 
  2185.       installLocations.push(InstallLocations.get(key));
  2186.     return new ArrayEnumerator(installLocations);
  2187.   },
  2188.   
  2189.   /**
  2190.    * Gets a named Install Location
  2191.    * @param   name
  2192.    *          The name of the Install Location to get
  2193.    */
  2194.   get: function(name) {
  2195.     return name in this._locations ? this._locations[name] : null;
  2196.   },
  2197.   
  2198.   /**
  2199.    * Registers an Install Location
  2200.    * @param   installLocation
  2201.    *          The Install Location to register
  2202.    */
  2203.   put: function(installLocation) {
  2204.     this._locations[installLocation.name] = installLocation;
  2205.   }
  2206. };
  2207.  
  2208. /**
  2209.  * Manages the Startup Cache. The Startup Cache is a representation
  2210.  * of the contents of extensions.cache, a list of all
  2211.  * items the Extension System knows about, whether or not they
  2212.  * are active or visible.
  2213.  */
  2214. var StartupCache = {
  2215.   /**
  2216.    * Location Name -> GUID hash of entries from the Startup Cache file
  2217.    * Each entry has the following properties:
  2218.    *  "descriptor"    The location on disk of the item
  2219.    *  "mtime"         The time the location was last modified
  2220.    *  "op"            Any pending operations on this item.
  2221.    *  "location"      The Install Location name where the item is installed.
  2222.    */
  2223.   entries: { },
  2224.  
  2225.   /**
  2226.    * Puts an entry into the Startup Cache
  2227.    * @param   installLocation
  2228.    *          The Install Location where the item is installed
  2229.    * @param   id
  2230.    *          The GUID of the item
  2231.    * @param   op
  2232.    *          The name of the operation to be performed
  2233.    * @param   shouldCreate
  2234.    *          Whether or not we should create a new entry for this item
  2235.    *          in the cache if one does not already exist. 
  2236.    */
  2237.   put: function(installLocation, id, op, shouldCreate) {
  2238.     var itemLocation = installLocation.getItemLocation(id);
  2239.  
  2240.     var descriptor = null;
  2241.     var mtime = null;
  2242.     if (itemLocation) {
  2243.       itemLocation.QueryInterface(nsILocalFile);
  2244.       descriptor = getDescriptorFromFile(itemLocation, installLocation);
  2245.       if (itemLocation.exists() && itemLocation.isDirectory())
  2246.         mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2247.     }
  2248.  
  2249.     this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
  2250.   },
  2251.  
  2252.   /**
  2253.    * Private helper function for putting an entry into the Startup Cache
  2254.    * without relying on the presence of its associated nsIInstallLocation
  2255.    * instance.
  2256.    *
  2257.    * @param key
  2258.    *        The install location name.
  2259.    * @param id
  2260.    *        The ID of the item.
  2261.    * @param descriptor
  2262.    *        Value returned from absoluteDescriptor.  May be null, in which
  2263.    *        case the descriptor field is not updated.
  2264.    * @param mtime
  2265.    *        The last modified time of the item.  May be null, in which case the
  2266.    *        descriptor field is not updated.
  2267.    * @param op
  2268.    *        The OP code to store with the entry.
  2269.    * @param shouldCreate
  2270.    *        Boolean value indicating whether to create or delete the entry.
  2271.    */
  2272.   _putRaw: function(key, id, descriptor, mtime, op, shouldCreate) {
  2273.     if (!(key in this.entries))
  2274.       this.entries[key] = { };
  2275.     if (!(id in this.entries[key]))
  2276.       this.entries[key][id] = { };
  2277.     if (shouldCreate) {
  2278.       if (!this.entries[key][id]) 
  2279.         this.entries[key][id] = { };
  2280.  
  2281.       var entry = this.entries[key][id];
  2282.  
  2283.       if (descriptor)
  2284.         entry.descriptor = descriptor;
  2285.       if (mtime) 
  2286.         entry.mtime = mtime;
  2287.       entry.op = op;
  2288.       entry.location = key;
  2289.     }
  2290.     else
  2291.       this.entries[key][id] = null;
  2292.   },
  2293.   
  2294.   /**
  2295.    * Clears an entry from the Startup Cache
  2296.    * @param   installLocation
  2297.    *          The Install Location where item is installed
  2298.    * @param   id
  2299.    *          The GUID of the item.
  2300.    */
  2301.   clearEntry: function(installLocation, id) {
  2302.     var key = installLocation.name;
  2303.     if (key in this.entries && id in this.entries[key])
  2304.       this.entries[key][id] = null;
  2305.   },
  2306.   
  2307.   /**
  2308.    * Get all the startup cache entries for a particular ID.
  2309.    * @param   id
  2310.    *          The GUID of the item to locate.
  2311.    * @returns An array of Startup Cache entries for the specified ID.
  2312.    */
  2313.   findEntries: function(id) {
  2314.     var entries = [];
  2315.     for (var key in this.entries) {
  2316.       if (id in this.entries[key]) 
  2317.         entries.push(this.entries[key][id]);
  2318.     }
  2319.     return entries;
  2320.   },
  2321.  
  2322.   /**
  2323.    * Call a function on each entry.  The callback function takes a single
  2324.    * parameter, which is an entry object.
  2325.    */
  2326.   forEachEntry: function(callback) {
  2327.     for (var key in this.entries) {
  2328.       for (id in this.entries[key])
  2329.         callback(this.entries[key][id]);
  2330.     }
  2331.   },
  2332.   
  2333.   /** 
  2334.    * Read the Item-Change manifest file into a hash of properties.
  2335.    * The Item-Change manifest currently holds a list of paths, with the last
  2336.    * mtime for each path, and the GUID of the item at that path.
  2337.    */
  2338.   read: function() {
  2339.     var itemChangeManifest = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2340.     if (!itemChangeManifest.exists()) {
  2341.       // There is no change manifest for some reason, either we're in an initial
  2342.       // state or something went wrong with one of the other files and the
  2343.       // change manifest was removed. Return an empty dataset and rebuild.
  2344.       return;
  2345.     }
  2346.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  2347.                         .createInstance(Components.interfaces.nsIFileInputStream);
  2348.     fis.init(itemChangeManifest, -1, -1, false);
  2349.     if (fis instanceof nsILineInputStream) {
  2350.       var line = { value: "" };
  2351.       var more = false;
  2352.       do {
  2353.         more = fis.readLine(line);
  2354.         if (line.value) {
  2355.           // The Item-Change manifest is formatted like so:
  2356.           //  (pd = descriptor)
  2357.           // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2358.           // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
  2359.           // ...
  2360.           // We hash on location-key first, because we don't want to have to 
  2361.           // spin up the main extensions datasource on every start to determine
  2362.           // the Install Location for an item.
  2363.           // We hash on guid second, because we want a way to quickly determine
  2364.           // item GUID during a check loop that runs on every startup.
  2365.           var parts = line.value.split("\t");
  2366.           var op = parts[4];
  2367.           this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
  2368.           if (op)
  2369.             PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
  2370.         }
  2371.       }
  2372.       while (more);
  2373.     }
  2374.     fis.close();
  2375.   },
  2376.  
  2377.   /**
  2378.    * Writes the Startup Cache to disk
  2379.    */
  2380.   write: function() {
  2381.     var extensionsCacheFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2382.     var fos = openSafeFileOutputStream(extensionsCacheFile);
  2383.     for (var locationKey in this.entries) {
  2384.       for (var id in this.entries[locationKey]) {
  2385.         var entry = this.entries[locationKey][id];
  2386.         if (entry) {
  2387.           try {
  2388.             var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
  2389.  
  2390.             // Update our knowledge of this item's last-modified-time.
  2391.             // XXXdarin: this may cause us to miss changes in some cases.
  2392.             var itemMTime = 0;
  2393.             if (itemLocation.exists() && itemLocation.isDirectory())
  2394.               itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2395.  
  2396.             // Each line in the startup cache manifest is in this form:
  2397.             // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2398.             var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
  2399.                        itemMTime + "\t" + entry.op + "\r\n";
  2400.             fos.write(line, line.length);
  2401.           }
  2402.           catch (e) {}
  2403.         }
  2404.       }
  2405.     }
  2406.     closeSafeFileOutputStream(fos);
  2407.   }
  2408. };
  2409.  
  2410. /**
  2411.  * Manages the Blocklist. The Blocklist is a representation of the contents of
  2412.  * blocklist.xml and allows us to remotely disable / re-enable blocklisted
  2413.  * items managed by the Extension Manager with an item's appDisabled property.
  2414.  */
  2415. var Blocklist = {
  2416.   /**
  2417.    * Extension ID -> array of Version Ranges
  2418.    * Each value in the version range array is a JS Object that has the
  2419.    * following properties:
  2420.    *   "minVersion"  The minimum version in a version range (default = 0)
  2421.    *   "maxVersion"  The maximum version in a version range (default = *)
  2422.    *   "targetApps"  Application ID -> array of Version Ranges
  2423.    *                 (default = current application ID)
  2424.    *                 Each value in the version range array is a JS Object that
  2425.    *                 has the following properties:
  2426.    *                   "minVersion"  The minimum version in a version range
  2427.    *                                 (default = 0)
  2428.    *                   "maxVersion"  The maximum version in a version range
  2429.    *                                 (default = *)
  2430.    */
  2431.   entries: null,
  2432.  
  2433.   notify: function() {
  2434.     if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false)
  2435.       return;
  2436.  
  2437.     try {
  2438.       var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
  2439.     }
  2440.     catch (e) {
  2441.       LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + 
  2442.           " is missing!");
  2443.       return;
  2444.     }
  2445.  
  2446.     dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
  2447.     dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
  2448.     // Verify that the URI is valid
  2449.     try {
  2450.       var uri = newURI(dsURI);
  2451.     }
  2452.     catch (e) {
  2453.       LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + 
  2454.           "for: " + dsURI + ", error: " + e);
  2455.       return;
  2456.     }
  2457.  
  2458.     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  2459.                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  2460.     request.open("GET", uri.spec, true);
  2461.     request.channel.notificationCallbacks = new BadCertHandler();
  2462.     request.overrideMimeType("text/xml");
  2463.     request.setRequestHeader("Cache-Control", "no-cache");
  2464.  
  2465.     var self = this;
  2466.     request.onerror = function(event) { self.onXMLError(event); };
  2467.     request.onload  = function(event) { self.onXMLLoad(event);  };
  2468.     request.send(null);
  2469.   },
  2470.  
  2471.   onXMLLoad: function(aEvent) {
  2472.     var request = aEvent.target;
  2473.     try {
  2474.       checkCert(request.channel);
  2475.     }
  2476.     catch (e) {
  2477.       LOG("Blocklist::onXMLLoad: " + e);
  2478.       return;
  2479.     }
  2480.     var responseXML = request.responseXML;
  2481.     if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
  2482.         (request.status != 200 && request.status != 0)) {
  2483.       LOG("Blocklist::onXMLLoad: there was an error during load");
  2484.       return;
  2485.     }
  2486.     var blocklistFile = getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
  2487.     if (blocklistFile.exists())
  2488.       blocklistFile.remove(false);
  2489.     var fos = openSafeFileOutputStream(blocklistFile);
  2490.     fos.write(request.responseText, request.responseText.length);
  2491.     closeSafeFileOutputStream(fos);
  2492.     this.entries = this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR,
  2493.                                                        [FILE_BLOCKLIST]));
  2494.     var em = Components.classes["@mozilla.org/extensions/manager;1"]
  2495.                        .getService(Components.interfaces.nsIExtensionManager)
  2496.                        .QueryInterface(Components.interfaces.nsIExtensionManager_MOZILLA_1_8_BRANCH);
  2497.     em.checkForBlocklistChanges();
  2498.   },
  2499.  
  2500.   onXMLError: function(aEvent) {
  2501.     try {
  2502.       var request = aEvent.target;
  2503.       // the following may throw (e.g. a local file or timeout)
  2504.       var status = request.status;
  2505.     }
  2506.     catch (e) {
  2507.       request = aEvent.target.channel.QueryInterface(Components.interfaces.nsIRequest);
  2508.       status = request.status;
  2509.     }
  2510.     var statusText = request.statusText;
  2511.     // When status is 0 we don't have a valid channel.
  2512.     if (status == 0)
  2513.       statusText = "nsIXMLHttpRequest channel unavailable";
  2514.     LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + 
  2515.         statusText);
  2516.   },
  2517.  
  2518.   /**
  2519.    * The blocklist XML file looks something like this:
  2520.    *
  2521.    * <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
  2522.    *   <emItems>
  2523.    *     <emItem id="item_1@domain">
  2524.    *       <versionRange minVersion="1.0" maxVersion="2.0.*">
  2525.    *         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
  2526.    *           <versionRange minVersion="1.5" maxVersion="1.5.*"/>
  2527.    *           <versionRange minVersion="1.7" maxVersion="1.7.*"/>
  2528.    *         </targetApplication>
  2529.    *         <targetApplication id="toolkit@mozilla.org">
  2530.    *           <versionRange minVersion="1.8" maxVersion="1.8.*"/>
  2531.    *         </targetApplication>
  2532.    *       </versionRange>
  2533.    *       <versionRange minVersion="3.0" maxVersion="3.0.*">
  2534.    *         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
  2535.    *           <versionRange minVersion="1.5" maxVersion="1.5.*"/>
  2536.    *         </targetApplication>
  2537.    *         <targetApplication id="toolkit@mozilla.org">
  2538.    *           <versionRange minVersion="1.8" maxVersion="1.8.*"/>
  2539.    *         </targetApplication>
  2540.    *       </versionRange>
  2541.    *     </emItem>
  2542.    *     <emItem id="item_2@domain">
  2543.    *       <versionRange minVersion="3.1" maxVersion="4.*"/>
  2544.    *     </emItem>
  2545.    *     <emItem id="item_3@domain">
  2546.    *       <versionRange>
  2547.    *         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
  2548.    *           <versionRange minVersion="1.5" maxVersion="1.5.*"/>
  2549.    *         </targetApplication>
  2550.    *       </versionRange>
  2551.    *     </emItem>
  2552.    *     <emItem id="item_4@domain">
  2553.    *       <versionRange>
  2554.    *         <targetApplication>
  2555.    *           <versionRange minVersion="1.5" maxVersion="1.5.*"/>
  2556.    *         </targetApplication>
  2557.    *       </versionRange>
  2558.    *     <emItem id="item_5@domain"/>
  2559.    *   </emItems>
  2560.    * </blocklist> 
  2561.    */
  2562.   _loadBlocklistFromFile: function(file) {
  2563.     if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) {
  2564.       LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
  2565.       return { };
  2566.     }
  2567.  
  2568.     if (!file.exists()) {
  2569.       LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist");
  2570.       return { };
  2571.     }
  2572.  
  2573.     var result = { };
  2574.     var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
  2575.                                .createInstance(Components.interfaces.nsIFileInputStream);
  2576.     fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0);
  2577.     try {
  2578.       var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  2579.                              .createInstance(Components.interfaces.nsIDOMParser);
  2580.       var doc = parser.parseFromStream(fileStream, "UTF-8", file.fileSize, "text/xml");
  2581.       if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
  2582.         LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
  2583.             "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
  2584.             "Received: " + doc.documentElement.namespaceURI);
  2585.         return { };
  2586.       }
  2587.  
  2588.       const kELEMENT_NODE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
  2589.       var itemNodes = this._getItemNodes(doc.documentElement.childNodes);
  2590.       for (var i = 0; i < itemNodes.length; ++i) {
  2591.         var blocklistElement = itemNodes[i];
  2592.         if (blocklistElement.nodeType != kELEMENT_NODE ||
  2593.             blocklistElement.localName != "emItem")
  2594.           continue;
  2595.  
  2596.         blocklistElement.QueryInterface(Components.interfaces.nsIDOMElement);
  2597.         var versionNodes = blocklistElement.childNodes;
  2598.         var id = blocklistElement.getAttribute("id");
  2599.         result[id] = [];
  2600.         for (var x = 0; x < versionNodes.length; ++x) {
  2601.           var versionRangeElement = versionNodes[x];
  2602.           if (versionRangeElement.nodeType != kELEMENT_NODE ||
  2603.               versionRangeElement.localName != "versionRange")
  2604.             continue;
  2605.  
  2606.           result[id].push(new BlocklistItemData(versionRangeElement));
  2607.         }
  2608.         // if only the extension ID is specified block all versions of the
  2609.         // extension for the current application.
  2610.         if (result[id].length == 0)
  2611.           result[id].push(new BlocklistItemData(null));
  2612.       }
  2613.     }
  2614.     catch (e) {
  2615.       LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
  2616.       return { };
  2617.     }
  2618.     fileStream.close();
  2619.     return result;
  2620.   },
  2621.  
  2622.   _getItemNodes: function(deChildNodes) {
  2623.     const kELEMENT_NODE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
  2624.     for (var i = 0; i < deChildNodes.length; ++i) {
  2625.       var emItemsElement = deChildNodes[i];
  2626.       if (emItemsElement.nodeType == kELEMENT_NODE &&
  2627.           emItemsElement.localName == "emItems")
  2628.         return emItemsElement.childNodes;
  2629.     }
  2630.     return [ ];
  2631.   },
  2632.  
  2633.   _ensureBlocklist: function() {
  2634.     if (!this.entries)
  2635.       this.entries = this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, 
  2636.                                                          [FILE_BLOCKLIST]));
  2637.   }
  2638. };
  2639.  
  2640. /**
  2641.  * Helper for constructing a blocklist.
  2642.  */
  2643. function BlocklistItemData(versionRangeElement) {
  2644.   var versionRange = this.getBlocklistVersionRange(versionRangeElement);
  2645.   this.minVersion = versionRange.minVersion;
  2646.   this.maxVersion = versionRange.maxVersion;
  2647.   this.targetApps = { };
  2648.   var found = false;
  2649.  
  2650.   if (versionRangeElement) {
  2651.     for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
  2652.       var targetAppElement = versionRangeElement.childNodes[i];
  2653.       if (targetAppElement.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE ||
  2654.           targetAppElement.localName != "targetApplication")
  2655.         continue;
  2656.       found = true;
  2657.       // default to the current application if id is not provided.
  2658.       var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
  2659.       this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
  2660.     }
  2661.   }
  2662.   // Default to all versions of the extension and the current application when
  2663.   // versionRange is not defined.
  2664.   if (!found)
  2665.     this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
  2666. }
  2667.  
  2668. BlocklistItemData.prototype = {
  2669. /**
  2670.  * Retrieves a version range (e.g. minVersion and maxVersion) for a
  2671.  * blocklist item's targetApplication element.
  2672.  * @param   targetAppElement
  2673.  *          A targetApplication blocklist element.
  2674.  * @returns An array of JS objects with the following properties:
  2675.  *          "minVersion"  The minimum version in a version range (default = 0).
  2676.  *          "maxVersion"  The maximum version in a version range (default = *).
  2677.  */
  2678.   getBlocklistAppVersions: function(targetAppElement) {
  2679.     var appVersions = [ ];
  2680.     var found = false;
  2681.  
  2682.     if (targetAppElement) {
  2683.       for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
  2684.         var versionRangeElement = targetAppElement.childNodes[i];
  2685.         if (versionRangeElement.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE ||
  2686.             versionRangeElement.localName != "versionRange")
  2687.           continue;
  2688.         found = true;
  2689.         appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
  2690.       }
  2691.     }
  2692.     // return minVersion = 0 and maxVersion = * if not available
  2693.     if (!found)
  2694.       return [ this.getBlocklistVersionRange(null) ];
  2695.     return appVersions;
  2696.   },
  2697.  
  2698. /**
  2699.  * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
  2700.  * versionRange element.
  2701.  * @param   versionRangeElement
  2702.  *          The versionRange blocklist element.
  2703.  * @returns A JS object with the following properties:
  2704.  *          "minVersion"  The minimum version in a version range (default = 0).
  2705.  *          "maxVersion"  The maximum version in a version range (default = *).
  2706.  */
  2707.   getBlocklistVersionRange: function(versionRangeElement) {
  2708.     var minVersion = "0";
  2709.     var maxVersion = "*";
  2710.     if (!versionRangeElement)
  2711.       return { minVersion: minVersion, maxVersion: maxVersion };
  2712.  
  2713.     if (versionRangeElement.hasAttribute("minVersion"))
  2714.       minVersion = versionRangeElement.getAttribute("minVersion");
  2715.     if (versionRangeElement.hasAttribute("maxVersion"))
  2716.       maxVersion = versionRangeElement.getAttribute("maxVersion");
  2717.  
  2718.     return { minVersion: minVersion, maxVersion: maxVersion };
  2719.   }
  2720. };
  2721.  
  2722. /**
  2723.  * Installs, manages and tracks compatibility for Extensions and Themes
  2724.  * @constructor
  2725.  */
  2726. function ExtensionManager() {
  2727.   gApp = Components.classes["@mozilla.org/xre/app-info;1"]
  2728.                    .getService(Components.interfaces.nsIXULAppInfo)
  2729.                    .QueryInterface(Components.interfaces.nsIXULRuntime);
  2730.   gOSTarget = gApp.OS;
  2731.   try {
  2732.     gXPCOMABI = gApp.XPCOMABI;
  2733.   } catch (ex) {
  2734.     // Provide a default for gXPCOMABI. It won't be compared to an
  2735.     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
  2736.     // as targetPlatform), but it will be displayed in error messages and
  2737.     // transmitted to update URLs.
  2738.     gXPCOMABI = UNKNOWN_XPCOM_ABI;
  2739.   }
  2740.   gPref = Components.classes["@mozilla.org/preferences-service;1"]
  2741.                     .getService(Components.interfaces.nsIPrefBranch2);
  2742.  
  2743.   gOS = Components.classes["@mozilla.org/observer-service;1"]
  2744.                   .getService(Components.interfaces.nsIObserverService);
  2745.   gOS.addObserver(this, "xpcom-shutdown", false);
  2746.  
  2747.   gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  2748.                        .getService(Components.interfaces.nsIConsoleService);  
  2749.   
  2750.   gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  2751.                    .getService(Components.interfaces.nsIRDFService);
  2752.   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  2753.   
  2754.   // Register Global Install Location
  2755.   var appGlobalExtensions = getDirNoCreate(KEY_APPDIR, [DIR_EXTENSIONS]);
  2756.   var priority = nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2757.   var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL, 
  2758.                                                     appGlobalExtensions, true,
  2759.                                                     priority);
  2760.   InstallLocations.put(globalLocation);
  2761.  
  2762.   // Register App-Profile Install Location
  2763.   var appProfileExtensions = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS]);
  2764.   var priority = nsIInstallLocation.PRIORITY_APP_PROFILE;
  2765.   var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE, 
  2766.                                                      appProfileExtensions, false,
  2767.                                                      priority);
  2768.   InstallLocations.put(profileLocation);
  2769.  
  2770. //@line 2696 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2771.   // Register HKEY_LOCAL_MACHINE Install Location
  2772.   InstallLocations.put(
  2773.       new WinRegInstallLocation("winreg-app-global",
  2774.                                 nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
  2775.                                 true,
  2776.                                 nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10));
  2777.  
  2778.   // Register HKEY_CURRENT_USER Install Location
  2779.   InstallLocations.put(
  2780.       new WinRegInstallLocation("winreg-app-user",
  2781.                                 nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
  2782.                                 false,
  2783.                                 nsIInstallLocation.PRIORITY_APP_SYSTEM_USER + 10));
  2784. //@line 2710 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2785.  
  2786.   // Register Additional Install Locations
  2787.   var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  2788.                                   .getService(Components.interfaces.nsICategoryManager);
  2789.   var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
  2790.   while (locations.hasMoreElements()) {
  2791.     var entry = locations.getNext().QueryInterface(Components.interfaces.nsISupportsCString).data;
  2792.     var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
  2793.     var location = Components.classes[contractID].getService(nsIInstallLocation);
  2794.     InstallLocations.put(location);
  2795.   }
  2796. }
  2797.  
  2798. ExtensionManager.prototype = {
  2799.   /**
  2800.    * See nsIObserver.idl
  2801.    */
  2802.   observe: function(subject, topic, data) {
  2803.     switch (topic) {
  2804.     case "app-startup":
  2805.       gOS.addObserver(this, "profile-after-change", false);
  2806.       break;
  2807.     case "profile-after-change":
  2808.       this._profileSelected();
  2809.       break;
  2810.     case "quit-application-requested":
  2811.       this._confirmCancelDownloadsOnQuit(subject);
  2812.       break;
  2813.     case "offline-requested":
  2814.       this._confirmCancelDownloadsOnOffline(subject);
  2815.       break;
  2816.     case "xpcom-shutdown":
  2817.       this._shutdown();
  2818.       break;
  2819.     case "nsPref:changed":
  2820.       if (data == PREF_EM_LOGGING_ENABLED)
  2821.         this._loggingToggled();
  2822.       else if (data == PREF_EM_CHECK_COMPATIBILITY)
  2823.         this._checkCompatToggled();
  2824.       break;
  2825.     }
  2826.   },
  2827.   
  2828.   /**
  2829.    * Refresh the logging enabled global from preferences when the user changes
  2830.    * the preference settting.
  2831.    */
  2832.   _loggingToggled: function() {
  2833.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2834.   },
  2835.  
  2836.   /**
  2837.    * Enables or disables extensions that are incompatible depending upon the pref
  2838.    * setting for compatibility checking.
  2839.    */
  2840.   _checkCompatToggled: function() {
  2841.     gCheckCompatibility = getPref("getBoolPref", PREF_EM_CHECK_COMPATIBILITY, true);
  2842.     var ds = this.datasource;
  2843.  
  2844.     // Enumerate all items
  2845.     var ctr = getContainer(ds, ds._itemRoot);
  2846.     var elements = ctr.GetElements();
  2847.     while (elements.hasMoreElements()) {
  2848.       var itemResource = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  2849.  
  2850.       // App disable or enable items as necessary
  2851.       // _appEnableItem and _appDisableItem will do nothing if the item is already
  2852.       // in the right state.
  2853.       id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  2854.       if (this._isUsableItem(id))
  2855.         this._appEnableItem(id);
  2856.       else
  2857.         this._appDisableItem(id);
  2858.     }
  2859.   },
  2860.  
  2861.   /**
  2862.    * Initialize the system after a profile has been selected.
  2863.    */  
  2864.   _profileSelected: function() {
  2865.     // Tell the Chrome Registry which Skin to select
  2866.     try {
  2867.       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
  2868.         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  2869.         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
  2870.         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
  2871.         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  2872.       }
  2873.     }
  2874.     catch (e) {
  2875.     }
  2876.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2877.     gCheckCompatibility = getPref("getBoolPref", PREF_EM_CHECK_COMPATIBILITY, true);
  2878.     gPref.addObserver("extensions.", this, false);
  2879.   },
  2880.  
  2881.   /**
  2882.    * Notify user that there are new addons updates
  2883.    */
  2884.   _showUpdatesWindow: function() {
  2885.     if (!getPref("getBoolPref", PREF_UPDATE_NOTIFYUSER, false))
  2886.       return;
  2887.  
  2888.     const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
  2889.     const EMFEATURES = "chrome,centerscreen,extra-chrome,dialog,resizable,modal";
  2890.  
  2891.     var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  2892.                        .getService(Components.interfaces.nsIWindowWatcher);
  2893.     var param = Components.classes["@mozilla.org/supports-array;1"]
  2894.                           .createInstance(Components.interfaces.nsISupportsArray);
  2895.     var arg = Components.classes["@mozilla.org/supports-string;1"]
  2896.                         .createInstance(Components.interfaces.nsISupportsString);
  2897.     arg.data = "updates-only";
  2898.     param.AppendElement(arg);
  2899.     ww.openWindow(null, EMURL, null, EMFEATURES, param);
  2900.   },
  2901.  
  2902.   /**
  2903.    * Clean up on application shutdown to avoid leaks.
  2904.    */
  2905.   _shutdown: function() {
  2906.     gOS.removeObserver(this, "xpcom-shutdown");
  2907.  
  2908.     // Release strongly held services.
  2909.     gOS = null;
  2910.     if (this._ds && gRDF) 
  2911.       gRDF.UnregisterDataSource(this._ds)
  2912.     gRDF = null;
  2913.     if (gPref)
  2914.       gPref.removeObserver("extensions.", this);
  2915.     gPref = null;
  2916.     gConsole = null;
  2917.     gVersionChecker = null;
  2918.     gInstallManifestRoot = null;
  2919.     gApp = null;
  2920.   },
  2921.   
  2922.   /**
  2923.    * Check for presence of critical Extension system files. If any is missing, 
  2924.    * delete the others and signal that the system needs to rebuild them all
  2925.    * from scratch.
  2926.    * @returns true if any critical file is missing and the system needs to
  2927.    *          be rebuilt, false otherwise.
  2928.    */
  2929.   _ensureDatasetIntegrity: function () {
  2930.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  2931.     var extensionsINI = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2932.     var extensionsCache = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2933.     
  2934.     var dsExists = extensionsDS.exists();
  2935.     var iniExists = extensionsINI.exists();
  2936.     var cacheExists = extensionsCache.exists();
  2937.  
  2938.     if (dsExists && iniExists && cacheExists)
  2939.       return false;
  2940.  
  2941.     // If any of the files are missing, remove the .ini file
  2942.     if (iniExists)
  2943.       extensionsINI.remove(false);
  2944.  
  2945.     // If the extensions datasource is missing remove the .cache file if it exists
  2946.     if (!dsExists && cacheExists)
  2947.       extensionsCache.remove(false);
  2948.  
  2949.     return true;
  2950.   },
  2951.   
  2952.   /**
  2953.    * See nsIExtensionManager.idl
  2954.    */
  2955.   start: function(commandLine) {
  2956.     var isDirty = false;
  2957.     var forceAutoReg = false;
  2958.     
  2959.     this._showUpdatesWindow();
  2960.     
  2961.     // Somehow the component list went away, and for that reason the new one
  2962.     // generated by this function is going to result in a different compreg.
  2963.     // We must force a restart.
  2964.     var componentList = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2965.     if (!componentList.exists())
  2966.       forceAutoReg = true;
  2967.     
  2968.     // Check for missing manifests - e.g. missing extensions.ini, missing
  2969.     // extensions.cache, extensions.rdf etc. If any of these files 
  2970.     // is missing then we are in some kind of weird or initial state and need
  2971.     // to force a regeneration.
  2972.     if (this._ensureDatasetIntegrity())
  2973.       isDirty = true;
  2974.  
  2975.     // Configure any items that are being installed, uninstalled or upgraded 
  2976.     // by being added, removed or modified by another process. We must do this
  2977.     // on every startup since there is no way we can tell if this has happened
  2978.     // or not!
  2979.     if (this._checkForFileChanges())
  2980.       isDirty = true;
  2981.  
  2982.     if (PendingOperations.size != 0)
  2983.       isDirty = true;
  2984.  
  2985.     // Extension Changes
  2986.     if (isDirty) {
  2987.       var needsRestart = this._finishOperations();
  2988.  
  2989.       if (forceAutoReg) {
  2990.         this._extensionListChanged = true;
  2991.         needsRestart = true;
  2992.       }
  2993.       return needsRestart;
  2994.     }
  2995.       
  2996.     this._startTimers();
  2997.  
  2998.     return false;
  2999.   },
  3000.   
  3001.   /**
  3002.    * Begins all background update check timers
  3003.    */
  3004.   _startTimers: function() {
  3005.     // Register a background update check timer
  3006.     var tm = 
  3007.         Components.classes["@mozilla.org/updates/timer-manager;1"]
  3008.                   .getService(Components.interfaces.nsIUpdateTimerManager);
  3009.     var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400); 
  3010.     tm.registerTimer("addon-background-update-timer", this, interval);
  3011.  
  3012.     interval = getPref("getIntPref", PREF_BLOCKLIST_INTERVAL, 86400); 
  3013.     tm.registerTimer("blocklist-background-update-timer", Blocklist, interval);
  3014.   },
  3015.   
  3016.   /**
  3017.    * Notified when a timer fires
  3018.    * @param   timer
  3019.    *          The timer that fired
  3020.    */
  3021.   notify: function(timer) {
  3022.     if (!getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
  3023.       return;
  3024.  
  3025.     var items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  3026.  
  3027.     var updater = new ExtensionItemUpdater(gApp.ID, gApp.version, this);
  3028.     updater._background = true;
  3029.     updater.checkForUpdates(items, items.length, false, null);
  3030.   },
  3031.   
  3032.   /**
  3033.    * See nsIExtensionManager.idl
  3034.    */
  3035.   handleCommandLineArgs: function(commandLine) {
  3036.     try {
  3037.       var globalExtension = commandLine.handleFlagWithParam("install-global-extension", false);
  3038.       if (globalExtension) {
  3039.         var file = commandLine.resolveFile(globalExtension);
  3040.         this._installGlobalItem(file);
  3041.       }
  3042.       var globalTheme = commandLine.handleFlagWithParam("install-global-theme", false);
  3043.       if (globalTheme) {
  3044.         file = commandLine.resolveFile(globalTheme);
  3045.         this._installGlobalItem(file);
  3046.       }
  3047.     }
  3048.     catch (e) { 
  3049.       LOG("ExtensionManager:handleCommandLineArgs - failure, catching exception - lineno: " +
  3050.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  3051.     }
  3052.     commandLine.preventDefault = true;
  3053.   },
  3054.  
  3055.   /**
  3056.    * Installs an XPI/JAR file into the KEY_APP_GLOBAL install location.
  3057.    * @param   file
  3058.    *          The XPI/JAR file to extract
  3059.    */
  3060.   _installGlobalItem: function(file) {
  3061.     if (!file || !file.exists())
  3062.       throw new Error("Unable to find the file specified on the command line!");
  3063. //@line 2989 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  3064.     // make sure the file is local on Windows
  3065.     file.normalize();
  3066.     if (file.path[1] != ':')
  3067.       throw new Error("Can't install global chrome from non-local file "+file.path);
  3068. //@line 2994 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  3069.     var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  3070.     if (!installManifestFile.exists())
  3071.       throw new Error("The package is missing an install manifest!");
  3072.     var installManifest = getInstallManifest(installManifestFile);
  3073.     installManifestFile.remove(false);
  3074.     var installData = this._getInstallData(installManifest);
  3075.     var installer = new Installer(installManifest, installData.id,
  3076.                                   InstallLocations.get(KEY_APP_GLOBAL),
  3077.                                   installData.type);
  3078.     installer._installExtensionFiles(file);
  3079.     if (installData.type == nsIUpdateItem.TYPE_THEME)
  3080.       installer.upgradeThemeChrome();
  3081.     else
  3082.       installer.upgradeExtensionChrome();
  3083.   },
  3084.  
  3085.   /**
  3086.    * Check to see if a file is a XPI/JAR file that the user dropped into this
  3087.    * Install Location. (i.e. a XPI that is not a staged XPI from an install 
  3088.    * transaction that is currently in operation). 
  3089.    * @param   file
  3090.    *          The XPI/JAR file to configure
  3091.    * @param   location
  3092.    *          The Install Location where this file was found.
  3093.    * @returns A nsIUpdateItem representing the dropped XPI if this file was a 
  3094.    *          XPI/JAR that needs installation, null otherwise.
  3095.    */
  3096.   _getItemForDroppedFile: function(file, location) {
  3097.     if (fileIsItemPackage(file)) {
  3098.       // We know nothing about this item, it is not something we've
  3099.       // staged in preparation for finalization, so assume it's something
  3100.       // the user dropped in.
  3101.       LOG("A Item Package appeared at: " + file.path + " that we know " + 
  3102.           "nothing about, assuming it was dropped in by the user and " + 
  3103.           "configuring for installation now. Location Key: " + location.name);
  3104.  
  3105.       var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  3106.       if (!installManifestFile.exists())
  3107.         return null;
  3108.       var installManifest = getInstallManifest(installManifestFile);
  3109.       installManifestFile.remove(false);
  3110.       var ds = this.datasource;
  3111.       var installData = this._getInstallData(installManifest);
  3112.       var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
  3113.       return makeItem(installData.id,
  3114.                       installData.version,
  3115.                       location.name,
  3116.                       targetAppInfo ? targetAppInfo.minVersion : "",
  3117.                       targetAppInfo ? targetAppInfo.maxVersion : "",
  3118.                       getManifestProperty(installManifest, "name"),
  3119.                       "", /* XPI Update URL */
  3120.                       "", /* XPI Update Hash */
  3121.                       getManifestProperty(installManifest, "iconURL"),
  3122.                       getManifestProperty(installManifest, "updateURL"),
  3123.                       installData.type);
  3124.     }
  3125.     return null;
  3126.   },
  3127.   
  3128.   /**
  3129.    * Check for changes to items that were made independently of the Extension 
  3130.    * Manager, e.g. items were added or removed from a Install Location or items
  3131.    * in an Install Location changed. 
  3132.    */
  3133.   _checkForFileChanges: function() {
  3134.     var em = this;
  3135.     /** 
  3136.      * Configure an item that was installed or upgraded by another process
  3137.      * so that |_finishOperations| can properly complete processing and 
  3138.      * registration. 
  3139.      * As this is the only point at which we can reliably know the Install
  3140.      * Location of this item, we use this as an opportunity to:
  3141.      * 1. Check that this item is compatible with this Firefox version.
  3142.      * 2. If it is, configure the item by using the supplied callback.
  3143.      *    We do not do any special handling in the case that the item is
  3144.      *    not compatible with this version other than to simply not register
  3145.      *    it and log that fact - there is no "phone home" check for updates. 
  3146.      *    It may or may not make sense to do this, but for now we'll just
  3147.      *    not register.
  3148.      * @param   id
  3149.      *          The GUID of the item to validate and configure.
  3150.      * @param   location
  3151.      *          The Install Location where this item is installed.
  3152.      * @param   callback
  3153.      *          The callback that configures the item for installation upon
  3154.      *          successful validation.
  3155.      */      
  3156.     function installItem(id, location, callback) {
  3157.       // As this is the only pint at which we reliably know the installation
  3158.       var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  3159.       if (installRDF.exists()) {
  3160.         LOG("Item Installed/Upgraded at Install Location: " + location.name + 
  3161.             " Item ID: " + id + ", attempting to register...");
  3162.         var installManifest = getInstallManifest(installRDF);
  3163.         var installData = em._getInstallData(installManifest);
  3164.         if (installData.error == INSTALLERROR_SUCCESS) {
  3165.           LOG("... success, item is compatible");
  3166.           callback(installManifest, installData.id, location, installData.type);
  3167.         }
  3168.         else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
  3169.           LOG("... success, item installed but is not compatible");
  3170.           callback(installManifest, installData.id, location, installData.type);
  3171.           em._appDisableItem(id);
  3172.         }
  3173.         else if (installData.error == INSTALLERROR_BLOCKLISTED) {
  3174.           LOG("... success, item installed but is blocklisted");
  3175.           callback(installManifest, installData.id, location, installData.type);
  3176.           em._appDisableItem(id);
  3177.         }
  3178.         else {
  3179.           /**
  3180.            * Turns an error code into a message for logging
  3181.            * @param   error
  3182.            *          an Install Error code
  3183.            * @returns A string message to be logged.
  3184.            */
  3185.           function translateErrorMessage(error) {
  3186.             switch (error) {
  3187.             case INSTALLERROR_INVALID_GUID:
  3188.               return "Invalid GUID";
  3189.             case INSTALLERROR_INVALID_VERSION:
  3190.               return "Invalid Version";
  3191.             case INSTALLERROR_INCOMPATIBLE_VERSION:
  3192.               return "Incompatible Version";
  3193.             case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  3194.               return "Incompatible Platform";
  3195.             }
  3196.           }
  3197.           LOG("... failure, item is not compatible, error: " + 
  3198.               translateErrorMessage(installData.error));
  3199.  
  3200.           // Add the item to the Startup Cache anyway, so we don't re-detect it
  3201.           // every time the app starts.
  3202.           StartupCache.put(location, id, OP_NONE, true);
  3203.         }
  3204.       }      
  3205.     }
  3206.   
  3207.     /**
  3208.      * Determines if an item can be used based on whether or not the install
  3209.      * location of the "item" has an equal or higher priority than the install
  3210.      * location where another version may live.
  3211.      * @param   id
  3212.      *          The GUID of the item being installed.
  3213.      * @param   location
  3214.      *          The location where an item is to be installed.
  3215.      * @returns true if the item can be installed at that location, false 
  3216.      *          otherwise.
  3217.      */
  3218.     function canUse(id, location) {
  3219.       for (var locationKey in StartupCache.entries) {
  3220.         if (locationKey != location.name && 
  3221.             id in StartupCache.entries[locationKey]) {
  3222.           if (StartupCache.entries[locationKey][id]) {
  3223.             var oldInstallLocation = InstallLocations.get(locationKey);
  3224.             if (oldInstallLocation.priority <= location.priority)
  3225.               return false;
  3226.           }
  3227.         }
  3228.       }
  3229.       return true;
  3230.     }
  3231.     
  3232.     /** 
  3233.       * Gets a Dialog Param Block loaded with a set of strings to initialize the
  3234.       * XPInstall Confirmation Dialog.
  3235.       * @param   strings
  3236.       *          An array of strings
  3237.       * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
  3238.       */
  3239.     function getParamBlock(strings) {
  3240.       var dpb = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  3241.                           .createInstance(Components.interfaces.nsIDialogParamBlock);
  3242.       // OK and Cancel Buttons
  3243.       dpb.SetInt(0, 2);
  3244.       // Number of Strings
  3245.       dpb.SetInt(1, strings.length);
  3246.       dpb.SetNumberStrings(strings.length);
  3247.       // Add Strings
  3248.       for (var i = 0; i < strings.length; ++i)
  3249.         dpb.SetString(i, strings[i]);
  3250.       
  3251.       var supportsString = Components.classes["@mozilla.org/supports-string;1"]
  3252.                                      .createInstance(Components.interfaces.nsISupportsString);
  3253.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3254.       supportsString.data = bundle.GetStringFromName("droppedInWarning");
  3255.       var objs = Components.classes["@mozilla.org/array;1"]
  3256.                            .createInstance(Components.interfaces.nsIMutableArray);
  3257.       objs.appendElement(supportsString, false);
  3258.       dpb.objects = objs;
  3259.       return dpb;        
  3260.     }
  3261.  
  3262.     /**
  3263.      * Installs a set of files which were dropped into an install location by 
  3264.      * the user, only after user confirmation.
  3265.      * @param   droppedInFiles
  3266.      *          An array of JS objects with the following properties:
  3267.      *          "file"      The nsILocalFile where the XPI lives
  3268.      *          "location"  The Install Location where the XPI was found. 
  3269.      * @param   xpinstallStrings
  3270.      *          An array of strings used to initialize the XPInstall Confirm 
  3271.      *          dialog.
  3272.      */ 
  3273.     function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
  3274.       if (droppedInFiles.length == 0) 
  3275.         return;
  3276.         
  3277.       var dpb = getParamBlock(xpinstallStrings);
  3278.       var ifptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
  3279.                             .createInstance(Components.interfaces.nsISupportsInterfacePointer);
  3280.       ifptr.data = dpb;
  3281.       ifptr.dataIID = Components.interfaces.nsIDialogParamBlock;
  3282.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3283.                           .getService(Components.interfaces.nsIWindowWatcher);
  3284.       ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG, 
  3285.                     "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
  3286.       if (!dpb.GetInt(0)) {
  3287.         // User said OK - install items
  3288.         for (var i = 0; i < droppedInFiles.length; ++i) {
  3289.           em.installItemFromFile(droppedInFiles[i].file, 
  3290.                                  droppedInFiles[i].location.name);
  3291.           // We are responsible for cleaning up this file
  3292.           droppedInFiles[i].file.remove(false);
  3293.         }
  3294.       }
  3295.       else {
  3296.         for (i = 0; i < droppedInFiles.length; ++i) {
  3297.           // We are responsible for cleaning up this file
  3298.           droppedInFiles[i].file.remove(false);
  3299.         }
  3300.       }
  3301.     }
  3302.     
  3303.     var isDirty = false;
  3304.     var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
  3305.                                      false);
  3306.     StartupCache.read();
  3307.     
  3308.     // Array of objects with 'location' and 'id' properties to maybe install.
  3309.     var newItems = [];
  3310.  
  3311.     var droppedInFiles = [];
  3312.     var xpinstallStrings = [];
  3313.     
  3314.     // Enumerate over the install locations from low to high priority.  The
  3315.     // enumeration returned is pre-sorted.
  3316.     var installLocations = this.installLocations;
  3317.     while (installLocations.hasMoreElements()) {
  3318.       var location = installLocations.getNext().QueryInterface(nsIInstallLocation);
  3319.  
  3320.       // Hash the set of items actually held by the Install Location.  
  3321.       var actualItems = { };
  3322.       var entries = location.itemLocations;
  3323.       while (true) {
  3324.         var entry = entries.nextFile;
  3325.         if (!entry)
  3326.           break;
  3327.  
  3328.         // Is this location a valid item? It must be a directory, and contain
  3329.         // an install.rdf manifest:
  3330.         if (entry.isDirectory()) {
  3331.           var installRDF = entry.clone();
  3332.           installRDF.append(FILE_INSTALL_MANIFEST);
  3333.  
  3334.           var id = location.getIDForLocation(entry);
  3335.           if (!id || (!installRDF.exists() && 
  3336.                       !location.itemIsManagedIndependently(id)))
  3337.             continue;
  3338.  
  3339.           actualItems[id] = entry;
  3340.         }
  3341.         else {
  3342.           // Check to see if this file is a XPI/JAR dropped into this dir
  3343.           // by the user, installing it if necessary. We do this here rather
  3344.           // than separately in |_finishOperations| because I don't want to
  3345.           // walk these lists multiple times on every startup.
  3346.           var item = this._getItemForDroppedFile(entry, location);
  3347.           if (item) {
  3348.             droppedInFiles.push({ file: entry, location: location });
  3349.  
  3350.             var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  3351.                                       .createInstance(Components.interfaces.nsIZipReader);
  3352.             zipReader.init(entry);
  3353.             var prettyName = "";
  3354.             try {
  3355.               var jar = zipReader.QueryInterface(Components.interfaces.nsIJAR);
  3356.               var principal = { };
  3357.               var certPrincipal = zipReader.getCertificatePrincipal(null, principal);
  3358.               // XXXbz This string could be empty.  This needs better
  3359.               // UI to present principal.value.certificate's subject.
  3360.               prettyName = principal.value.prettyName;
  3361.             }
  3362.             catch (e) { }
  3363.             xpinstallStrings = xpinstallStrings.concat([item.name, 
  3364.                                                         getURLSpecFromFile(entry),
  3365.                                                         item.iconURL, 
  3366.                                                         prettyName]);
  3367.             isDirty = true;
  3368.           }
  3369.         }
  3370.       }
  3371.       
  3372.       if (location.name in StartupCache.entries) {
  3373.         // Look for items that have been uninstalled by removing their directory.
  3374.         for (var id in StartupCache.entries[location.name]) {
  3375.           if (!StartupCache.entries[location.name] ||
  3376.               !StartupCache.entries[location.name][id]) 
  3377.             continue;
  3378.  
  3379.           // Force _finishOperations to run if we have enabled or disabled items.
  3380.           // XXXdarin this should be unnecessary now that we check
  3381.           // PendingOperations.size in start()
  3382.           if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
  3383.               StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
  3384.             isDirty = true;
  3385.           
  3386.           if (!(id in actualItems) && 
  3387.               StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL &&
  3388.               StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
  3389.               StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
  3390.             // We have an entry for this id in the Extensions database, for this 
  3391.             // install location, but it no longer exists in the Install Location. 
  3392.             // We can infer from this that the item has been removed, so uninstall
  3393.             // it properly. 
  3394.             if (canUse(id, location)) {
  3395.               LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor + 
  3396.                   " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
  3397.               
  3398.               // Load the Extensions Datasource and force this item into the visible
  3399.               // items list if it is not already. This allows us to handle the case 
  3400.               // where there is an entry for an item in the Startup Cache but not
  3401.               // in the extensions.rdf file - in that case the item will not be in
  3402.               // the visible list and calls to |getInstallLocation| will mysteriously
  3403.               // fail.
  3404.               this.datasource.updateVisibleList(id, location.name, false);
  3405.               this.uninstallItem(id);
  3406.               isDirty = true;
  3407.             }
  3408.           }
  3409.           else if (!ignoreMTimeChanges) {
  3410.             // Look for items whose mtime has changed, and as such we can assume 
  3411.             // they have been "upgraded".
  3412.             var lf = { path: StartupCache.entries[location.name][id].descriptor };
  3413.             try {
  3414.                lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
  3415.             }
  3416.             catch (e) { }
  3417.  
  3418.             if (lf.exists && lf.exists()) {
  3419.               var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
  3420.               if (actualMTime != StartupCache.entries[location.name][id].mtime) {
  3421.                 LOG("Item Location path changed: " + lf.path + " Item ID: " + 
  3422.                     id + " Location Key: " + location.name + ", attempting to upgrade item...");
  3423.                 if (canUse(id, location)) {
  3424.                   installItem(id, location, 
  3425.                               function(installManifest, id, location, type) {
  3426.                                 em._upgradeItem(installManifest, id, location, 
  3427.                                                 type);
  3428.                               });
  3429.                   isDirty = true;
  3430.                 }
  3431.               }
  3432.             }
  3433.             else {
  3434.               isDirty = true;
  3435.               LOG("Install Location returned a missing or malformed item path! " + 
  3436.                   "Item Path: " + lf.path + ", Location Key: " + location.name + 
  3437.                   " Item ID: " + id);
  3438.               if (canUse(id, location)) {
  3439.                 // Load the Extensions Datasource and force this item into the visible
  3440.                 // items list if it is not already. This allows us to handle the case 
  3441.                 // where there is an entry for an item in the Startup Cache but not
  3442.                 // in the extensions.rdf file - in that case the item will not be in
  3443.                 // the visible list and calls to |getInstallLocation| will mysteriously
  3444.                 // fail.
  3445.                 this.datasource.updateVisibleList(id, location.name, false);
  3446.                 this.uninstallItem(id);
  3447.               }
  3448.             }
  3449.           }
  3450.         }
  3451.       }
  3452.  
  3453.       // Look for items that have been installed by appearing in the location.
  3454.       for (var id in actualItems) {
  3455.         if (!(location.name in StartupCache.entries) || 
  3456.             !(id in StartupCache.entries[location.name]) ||
  3457.             !StartupCache.entries[location.name][id]) {
  3458.           // Remember that we've seen this item
  3459.           StartupCache.put(location, id, OP_NONE, true);
  3460.           // Push it on the stack of items to maybe install later
  3461.           newItems.push({location: location, id: id});
  3462.         }
  3463.       }
  3464.     }
  3465.  
  3466.     // Process any newly discovered items.  We do this here instead of in the
  3467.     // previous loop so that we can be sure that we have a fully populated
  3468.     // StartupCache.
  3469.     for (var i = 0; i < newItems.length; ++i) {
  3470.       var id = newItems[i].id;
  3471.       var location = newItems[i].location;
  3472.       if (canUse(id, location)) {
  3473.         LOG("Item Installed via directory addition to Install Location: " + 
  3474.             location.name + " Item ID: " + id + ", attempting to register...");
  3475.         installItem(id, location, 
  3476.                     function(installManifest, id, location, type) { 
  3477.                       em._configureForthcomingItem(installManifest, id, location, 
  3478.                                                    type);
  3479.                     });
  3480.         // Disable add-ons on install when the InstallDisabled file exists.
  3481.         // This is so Talkback will be disabled on a subset of installs.
  3482.         var installDisabled = location.getItemFile(id, "InstallDisabled");
  3483.         if (installDisabled.exists())
  3484.           em.disableItem(id);
  3485.         isDirty = true;
  3486.       }
  3487.     }
  3488.  
  3489.     // Ask the user if they want to install the dropped items, for security
  3490.     // purposes.
  3491.     installDroppedInFiles(droppedInFiles, xpinstallStrings);
  3492.     
  3493.     return isDirty;
  3494.   },
  3495.   
  3496.   /**
  3497.    * Upgrades contents.rdf files to chrome.manifest files for any existing
  3498.    * Extensions and Themes.
  3499.    * @returns true if actions were performed that require a restart, false 
  3500.    *          otherwise.
  3501.    */
  3502.   _upgradeChrome: function() {
  3503.     if (inSafeMode())
  3504.       return false;
  3505.  
  3506.     var checkForNewChrome = false;
  3507.     var ds = this.datasource;
  3508.     // If we have extensions that were installed before the new flat chrome
  3509.     // manifests, and are still valid, we need to manually create the flat
  3510.     // manifest files.
  3511.     var extensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION +
  3512.                                           nsIUpdateItem.TYPE_LOCALE +
  3513.                                           nsIUpdateItem.TYPE_PLUGIN);
  3514.     for (var i = 0; i < extensions.length; ++i) {
  3515.       var e = extensions[i];
  3516.       var itemLocation = e.location.getItemLocation(e.id);
  3517.       var manifest = itemLocation.clone();
  3518.       manifest.append(FILE_CHROME_MANIFEST);
  3519.       if (!manifest.exists()) {
  3520.         var installRDF = itemLocation.clone();
  3521.         installRDF.append(FILE_INSTALL_MANIFEST);
  3522.         var installLocation = this.getInstallLocation(e.id);
  3523.         if (installLocation && installRDF.exists()) {
  3524.           var itemLocation = installLocation.getItemLocation(e.id);
  3525.           if (itemLocation.exists() && itemLocation.isDirectory()) {
  3526.             var installer = new Installer(ds, e.id, installLocation, 
  3527.                                           nsIUpdateItem.TYPE_EXTENSION);
  3528.             installer.upgradeExtensionChrome();
  3529.           }
  3530.         }
  3531.         else {
  3532.           ds.removeItemMetadata(e.id);
  3533.           ds.removeItemFromContainer(e.id);
  3534.         }
  3535.  
  3536.         checkForNewChrome = true;
  3537.       }
  3538.     }
  3539.  
  3540.     var themes = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  3541.     // If we have themes that were installed before the new flat chrome
  3542.     // manifests, and are still valid, we need to manually create the flat
  3543.     // manifest files.
  3544.     for (i = 0; i < themes.length; ++i) {
  3545.       var item = themes[i];
  3546.       var itemLocation = item.location.getItemLocation(item.id);
  3547.       var manifest = itemLocation.clone();
  3548.       manifest.append(FILE_CHROME_MANIFEST);
  3549.       if (manifest.exists() ||
  3550.           item.id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  3551.         continue;
  3552.  
  3553.       var entries;
  3554.       try {
  3555.         var manifestURI = getURIFromFile(manifest);
  3556.         var chromeDir = itemLocation.clone();
  3557.         chromeDir.append(DIR_CHROME);
  3558.         
  3559.         if (!chromeDir.exists() || !chromeDir.isDirectory()) {
  3560.           ds.removeItemMetadata(item.id);
  3561.           ds.removeItemFromContainer(item.id);
  3562.           continue;
  3563.         }
  3564.  
  3565.         // We're relying on the fact that there is only one JAR file
  3566.         // in the "chrome" directory. This is a hack, but it works.
  3567.         entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  3568.         var jarFile = entries.nextFile;
  3569.         if (jarFile) {
  3570.           var jarFileURI = getURIFromFile(jarFile);
  3571.           var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  3572.  
  3573.           // Use the Chrome Registry API to install the theme there
  3574.           var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  3575.                             .getService(Components.interfaces.nsIToolkitChromeRegistry);
  3576.           cr.processContentsManifest(contentsURI, manifestURI, contentsURI, false, true);
  3577.         }
  3578.         entries.close();
  3579.       }
  3580.       catch (e) {
  3581.         LOG("_upgradeChrome: failed to upgrade contents manifest for " + 
  3582.             "theme: " + item.id + ", exception: " + e + "... The theme will be " + 
  3583.             "disabled.");
  3584.         this._appDisableItem(item.id);
  3585.       }
  3586.       finally {
  3587.         try {
  3588.           entries.close();
  3589.         }
  3590.         catch (e) {
  3591.         }
  3592.       }
  3593.       checkForNewChrome = true;
  3594.     }
  3595.     return checkForNewChrome;  
  3596.   },
  3597.   
  3598.   _checkForUncoveredItem: function(id) {
  3599.     var ds = this.datasource;
  3600.     var oldLocation = this.getInstallLocation(id);
  3601.     var newLocations = [];
  3602.     for (var locationKey in StartupCache.entries) {
  3603.       var location = InstallLocations.get(locationKey);
  3604.       if (id in StartupCache.entries[locationKey] && 
  3605.           location.priority > oldLocation.priority)
  3606.         newLocations.push(location);
  3607.     }
  3608.     newLocations.sort(function(a, b) { return b.priority - a.priority; });
  3609.     if (newLocations.length > 0) {
  3610.       for (var i = 0; i < newLocations.length; ++i) {
  3611.         // Check to see that the item at the location exists
  3612.         var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
  3613.         if (installRDF.exists()) {
  3614.           // Update the visible item cache so that |_finalizeUpgrade| is properly 
  3615.           // called from |_finishOperations|
  3616.           var name = newLocations[i].name;
  3617.           ds.updateVisibleList(id, name, true);
  3618.           PendingOperations.addItem(OP_NEEDS_UPGRADE, 
  3619.                                     { locationKey: name, id: id });
  3620.           PendingOperations.addItem(OP_NEEDS_INSTALL, 
  3621.                                     { locationKey: name, id: id });
  3622.           break;
  3623.         }
  3624.         else {
  3625.           // If no item exists at the location specified, remove this item
  3626.           // from the visible items list and check again. 
  3627.           StartupCache.clearEntry(newLocations[i], id);
  3628.           ds.updateVisibleList(id, null, true);
  3629.         }
  3630.       }
  3631.     }
  3632.     else
  3633.       ds.updateVisibleList(id, null, true);
  3634.   },
  3635.   
  3636.   /**
  3637.    * Finish up pending operations - perform upgrades, installs, enables/disables, 
  3638.    * uninstalls etc.
  3639.    * @returns true if actions were performed that require a restart, false 
  3640.    *          otherwise.
  3641.    */
  3642.   _finishOperations: function() {
  3643.     try {
  3644.       // Stuff has changed, load the Extensions datasource in all its RDFey
  3645.       // glory. 
  3646.       var ds = this.datasource;
  3647.       var updatedTargetAppInfos = [];
  3648.  
  3649.       var needsRestart = false;      
  3650.       do {
  3651.         // Enable and disable during startup so items that are changed in the
  3652.         // ui can be reset to a no-op.
  3653.         // Look for extensions that need to be enabled.
  3654.         var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
  3655.         for (var i = items.length - 1; i >= 0; --i) {
  3656.           var id = items[i].id;
  3657.           var installLocation = this.getInstallLocation(id);
  3658.           StartupCache.put(installLocation, id, OP_NONE, true);
  3659.           PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
  3660.           needsRestart = true;
  3661.         }
  3662.         PendingOperations.clearItems(OP_NEEDS_ENABLE);
  3663.  
  3664.         // Look for extensions that need to be disabled.
  3665.         items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
  3666.         for (i = items.length - 1; i >= 0; --i) {
  3667.           id = items[i].id;
  3668.           installLocation = this.getInstallLocation(id);
  3669.           StartupCache.put(installLocation, id, OP_NONE, true);
  3670.           PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
  3671.           needsRestart = true;
  3672.         }
  3673.         PendingOperations.clearItems(OP_NEEDS_DISABLE);
  3674.  
  3675.         // Look for extensions that need to be upgraded. The process here is to
  3676.         // uninstall the old version of the extension first, then install the
  3677.         // new version in its place. 
  3678.         items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  3679.         for (i = items.length - 1; i >= 0; --i) {
  3680.           id = items[i].id;
  3681.           var oldLocation = this.getInstallLocation(id);
  3682.           var newLocation = InstallLocations.get(items[i].locationKey);
  3683.           if (newLocation.priority <= oldLocation.priority) {
  3684.             // check if there is updated app compatibility info
  3685.             var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3686.             if (newTargetAppInfo)
  3687.               updatedTargetAppInfos.push(newTargetAppInfo);
  3688.             this._finalizeUpgrade(id);
  3689.           }
  3690.         }
  3691.         PendingOperations.clearItems(OP_NEEDS_UPGRADE);
  3692.  
  3693.         // Install items
  3694.         items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3695.         for (i = items.length - 1; i >= 0; --i) {
  3696.           needsRestart = true;
  3697.           id = items[i].id;
  3698.           // check if there is updated app compatibility info
  3699.           newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3700.           if (newTargetAppInfo)
  3701.             updatedTargetAppInfos.push(newTargetAppInfo);
  3702.           this._finalizeInstall(id, null);
  3703.         }
  3704.         PendingOperations.clearItems(OP_NEEDS_INSTALL);
  3705.  
  3706.         // Look for extensions that need to be removed. This MUST be done after
  3707.         // the install operations since extensions to be installed may have to be
  3708.         // uninstalled if there are errors during the installation process!
  3709.         items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
  3710.         for (i = items.length - 1; i >= 0; --i) {
  3711.           id = items[i].id;
  3712.           this._finalizeUninstall(id);
  3713.           this._checkForUncoveredItem(id);
  3714.           needsRestart = true;
  3715.         }
  3716.         PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
  3717.  
  3718.         // When there have been operations and all operations have completed.
  3719.         if (PendingOperations.size == 0) {
  3720.           // If there is updated app compatibility info update the data sources.
  3721.           for (i = 0; i < updatedTargetAppInfos.length; ++i)
  3722.             ds.updateTargetAppInfo(updatedTargetAppInfos[i].id,
  3723.                                    updatedTargetAppInfos[i].minVersion,
  3724.                                    updatedTargetAppInfos[i].maxVersion);
  3725.  
  3726.           // Enumerate all items
  3727.           var ctr = getContainer(ds, ds._itemRoot);
  3728.           var elements = ctr.GetElements();
  3729.           while (elements.hasMoreElements()) {
  3730.             var itemResource = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  3731.  
  3732.             // Ensure appDisabled is in the correct state.
  3733.             id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3734.             if (this._isUsableItem(id))
  3735.               ds.setItemProperty(id, EM_R("appDisabled"), null);
  3736.             else
  3737.               ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  3738.  
  3739.             // userDisabled is set based on its value being OP_NEEDS_ENABLE or
  3740.             // OP_NEEDS_DISABLE. This allows us to have an item to be enabled
  3741.             // by the app and disabled by the user during a single restart.
  3742.             var value = stringData(ds.GetTarget(itemResource, EM_R("userDisabled"), true));
  3743.             if (value == OP_NEEDS_ENABLE)
  3744.               ds.setItemProperty(id, EM_R("userDisabled"), null);
  3745.             else if (value == OP_NEEDS_DISABLE)
  3746.               ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  3747.           }
  3748.         }
  3749.       }
  3750.       while (PendingOperations.size > 0);
  3751.       
  3752.       // Upgrade contents.rdf files to the new chrome.manifest format for
  3753.       // existing Extensions and Themes
  3754.       if (this._upgradeChrome()) {
  3755.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  3756.                            .getService(Components.interfaces.nsIChromeRegistry);
  3757.         cr.checkForNewChrome();
  3758.       }
  3759.  
  3760.       // If no additional restart is required, it implies that there are
  3761.       // no new components that need registering so we can inform the app
  3762.       // not to do any extra startup checking next time round. 
  3763.       this._updateManifests(needsRestart);
  3764.  
  3765.     }
  3766.     catch (e) {
  3767.       LOG("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
  3768.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  3769.     }
  3770.     return needsRestart;
  3771.   },
  3772.   
  3773.   /**
  3774.    * Checks to see if there are items that are incompatible with this version
  3775.    * of the application, disables them to prevent incompatibility problems and 
  3776.    * invokes the Update Wizard to look for newer versions.
  3777.    * @returns true if there were incompatible items installed and disabled, and
  3778.    *          the application must now be restarted to reinitialize XPCOM,
  3779.    *          false otherwise.
  3780.    */
  3781.   checkForMismatches: function() {
  3782.     // Check to see if the version of the application that is being started
  3783.     // now is the same one that was started last time. 
  3784.     var currAppVersion = gApp.version;
  3785.     var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
  3786.     if (currAppVersion == lastAppVersion)
  3787.       return false;
  3788.     // With a new profile lastAppVersion doesn't exist yet.
  3789.     if (!lastAppVersion) {
  3790.       gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3791.       return false;
  3792.     }
  3793.  
  3794.     // Version mismatch, we have to load the extensions datasource and do
  3795.     // version checking. Time hit here doesn't matter since this doesn't happen
  3796.     // all that often.
  3797.     this._upgradeFromV10();
  3798.     
  3799.     // Make the extensions datasource consistent if it isn't already.
  3800.     var isDirty = false;
  3801.     if (this._ensureDatasetIntegrity())
  3802.       isDirty = true;
  3803.  
  3804.     if (this._checkForFileChanges())
  3805.       isDirty = true;
  3806.  
  3807.     if (PendingOperations.size != 0)
  3808.       isDirty = true;
  3809.  
  3810.     if (isDirty)
  3811.       this._finishOperations();
  3812.  
  3813.     var ds = this.datasource;
  3814.     // During app upgrade cleanup invalid entries in the extensions datasource.
  3815.     ds.beginUpdateBatch();
  3816.     var allResources = ds.GetAllResources();
  3817.     while (allResources.hasMoreElements()) {
  3818.       var res = allResources.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  3819.       if (ds.GetTarget(res, EM_R("downloadURL"), true) ||
  3820.           (!ds.GetTarget(res, EM_R("installLocation"), true) &&
  3821.           stringData(ds.GetTarget(res, EM_R("appDisabled"), true)) == "true"))
  3822.         ds.removeDownload(res.Value);
  3823.     }
  3824.     ds.endUpdateBatch();
  3825.  
  3826.     var allAppManaged = true;
  3827.     var ctr = getContainer(ds, ds._itemRoot);
  3828.     var elements = ctr.GetElements();
  3829.     while (elements.hasMoreElements()) {
  3830.       var itemResource = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  3831.       var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3832.       if (ds.getItemProperty(id, "appManaged") == "true") {
  3833.         // Force an update of the metadata for appManaged extensions since the
  3834.         // last modified time is not updated for directories on FAT / FAT32
  3835.         // filesystems when software update applies a new version of the app.
  3836.         var location = this.getInstallLocation(id);
  3837.         if (location.name == KEY_APP_GLOBAL) {
  3838.           var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  3839.           if (installRDF.exists()) {
  3840.             var metadataDS = getInstallManifest(installRDF);
  3841.             ds.addItemMetadata(id, metadataDS, location);
  3842.             ds.updateProperty(id, "compatible");
  3843.           }
  3844.         }
  3845.       }
  3846.       else if (allAppManaged)
  3847.         allAppManaged = false;
  3848.       // appDisabled is determined by an item being compatible,
  3849.       // satisfying its dependencies, and not being blocklisted
  3850.       if (this._isUsableItem(id)) {
  3851.         if (ds.getItemProperty(id, "appDisabled"))
  3852.           ds.setItemProperty(id, EM_R("appDisabled"), null);
  3853.       }
  3854.       else if (!ds.getItemProperty(id, "appDisabled"))
  3855.         ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  3856.  
  3857.       ds.setItemProperty(id, EM_R("availableUpdateURL"), null);
  3858.       ds.setItemProperty(id, EM_R("availableUpdateVersion"), null);
  3859.     }
  3860.     // Update the manifests to reflect the items that were disabled / enabled.
  3861.     this._updateManifests(true);
  3862.  
  3863.     // Always check for compatibility updates when upgrading if we have add-ons
  3864.     // that aren't managed by the application.
  3865.     if (!allAppManaged)
  3866.       this._showMismatchWindow();
  3867.     
  3868.     // Finish any pending upgrades from the compatibility update to avoid an
  3869.     // additional restart.
  3870.     if (PendingOperations.size != 0)
  3871.       this._finishOperations();
  3872.  
  3873.     // Update the last app version so we don't do this again with this version.
  3874.     gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3875.  
  3876.     // Prevent extension update dialog from showing
  3877.     gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
  3878.     return true;
  3879.   },
  3880.  
  3881.   /**
  3882.    * Shows the "Compatibility Updates" UI
  3883.    */
  3884.   _showMismatchWindow: function(items) {
  3885.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3886.                        .getService(Components.interfaces.nsIWindowMediator);
  3887.     var wizard = wm.getMostRecentWindow("Update:Wizard");
  3888.     if (wizard)
  3889.       wizard.focus();
  3890.     else {
  3891.       var features = "chrome,centerscreen,dialog,titlebar,modal";
  3892.       // This *must* be modal so as not to break startup! This code is invoked before
  3893.       // the main event loop is initiated (via checkForMismatches).
  3894.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3895.                          .getService(Components.interfaces.nsIWindowWatcher);
  3896.       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, null);
  3897.     }
  3898.   },
  3899.   
  3900.   /*
  3901.    * Catch all for facilitating a version 1.0 profile upgrade.
  3902.    * 1) removes the abandoned default theme directory from the profile.
  3903.    * 2) prepares themes installed with version 1.0 for installation.
  3904.    * 3) initiates an install to populate the new extensions datasource.
  3905.    * 4) migrates the disabled attribute from the old datasource.
  3906.    * 5) migrates the app compatibility info from the old datasource.
  3907.    */
  3908.   _upgradeFromV10: function() {
  3909.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  3910.     var dsExists = extensionsDS.exists();
  3911.     // Toolkiit 1.7 profiles (Firefox 1.0, Thunderbird 1.0, etc.) have a default
  3912.     // theme directory in the profile's extensions directory that will be
  3913.     // disabled due to having a maxVersion that is incompatible with the
  3914.     // toolkit 1.8 release of the app.
  3915.     var profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3916.                                              stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)]);
  3917.     if (profileDefaultTheme && profileDefaultTheme.exists()) {
  3918.       removeDirRecursive(profileDefaultTheme);
  3919.       // Sunbird 0.3a1 didn't move the default theme into the app's extensions
  3920.       // directory and we can't install it while uninstalling the one in the
  3921.       // profile directory. If we have a toolkit 1.8 extensions datasource and
  3922.       // a profile default theme deleting the toolkit 1.8 extensions datasource
  3923.       // will fix this problem when the datasource is re-created.
  3924.       if (dsExists)
  3925.         extensionsDS.remove(false);
  3926.     }
  3927.  
  3928.     // return early if the toolkit 1.7 extensions datasource file doesn't exist.
  3929.     var oldExtensionsFile = getFile(KEY_PROFILEDIR, [DIR_EXTENSIONS, "Extensions.rdf"]);
  3930.     if (!oldExtensionsFile.exists())
  3931.       return;
  3932.  
  3933.     // Sunbird 0.2 used a different GUID for the default theme
  3934.     profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3935.                                          "{8af2d0a7-e394-4de2-ae55-2dae532a7a9b}"]);
  3936.     if (profileDefaultTheme && profileDefaultTheme.exists())
  3937.       removeDirRecursive(profileDefaultTheme);
  3938.  
  3939.     // Firefox 0.9 profiles may have DOMi 1.0 with just an install.rdf
  3940.     var profileDOMi = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3941.                                      "{641d8d09-7dda-4850-8228-ac0ab65e2ac9}"]);
  3942.     if (profileDOMi && profileDOMi.exists())
  3943.       removeDirRecursive(profileDOMi);
  3944.  
  3945.     // return early to avoid migrating data twice if we already have a
  3946.     // toolkit 1.8 extension datasource.
  3947.     if (dsExists)
  3948.       return;
  3949.  
  3950.     // Prepare themes for installation
  3951.     // Only enumerate directories in the app-profile and app-global locations.
  3952.     var locations = [KEY_APP_PROFILE, KEY_APP_GLOBAL];
  3953.     for (var i = 0; i < locations.length; ++i) {
  3954.       var location = InstallLocations.get(locations[i]);
  3955.       if (!location.canAccess)
  3956.         continue;
  3957.  
  3958.       var entries = location.itemLocations;
  3959.       var entry;
  3960.       while ((entry = entries.nextFile)) {
  3961.         var installRDF = entry.clone();
  3962.         installRDF.append(FILE_INSTALL_MANIFEST);
  3963.  
  3964.         var chromeDir = entry.clone();
  3965.         chromeDir.append(DIR_CHROME);
  3966.  
  3967.         // It must be a directory without an install.rdf and it must contain
  3968.         // a chrome directory
  3969.         if (!entry.isDirectory() || installRDF.exists() || !chromeDir.exists())
  3970.           continue;
  3971.  
  3972.         var chromeEntries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  3973.         if (!chromeEntries.hasMoreElements())
  3974.           continue;
  3975.  
  3976.         // We're relying on the fact that there is only one JAR file
  3977.         // in the "chrome" directory. This is a hack, but it works.
  3978.         var jarFile = chromeEntries.nextFile;
  3979.         if (jarFile.isDirectory())
  3980.           continue;
  3981.         var id = location.getIDForLocation(entry);
  3982.  
  3983.         try {
  3984.           var zipReader = getZipReaderForFile(jarFile);
  3985.           zipReader.extract(FILE_INSTALL_MANIFEST, installRDF);
  3986.  
  3987.           var contentsManifestFile = location.getItemFile(id, FILE_CONTENTS_MANIFEST);
  3988.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  3989.  
  3990.           var rootFiles = ["preview.png", "icon.png"];
  3991.           for (var i = 0; i < rootFiles.length; ++i) {
  3992.             try {
  3993.               var target = location.getItemFile(id, rootFiles[i]);
  3994.               zipReader.extract(rootFiles[i], target);
  3995.             }
  3996.             catch (e) {
  3997.             }
  3998.           }
  3999.           zipReader.close();
  4000.         }
  4001.         catch (e) {
  4002.           LOG("ExtensionManager:_upgradeFromV10 - failed to extract theme files\r\n" +
  4003.               "Exception: " + e);
  4004.         }
  4005.       }
  4006.     }
  4007.  
  4008.     // When upgrading from a version 1.0 profile we need to populate the
  4009.     // extensions datasource with all items before checking for incompatible
  4010.     // items since the datasource hasn't been created yet.
  4011.     var itemsToCheck = [];
  4012.     if (this._checkForFileChanges()) {
  4013.       // Create a list of all items that are to be installed so we can migrate
  4014.       // these items's settings to the new datasource.
  4015.       var items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  4016.       for (i = items.length - 1; i >= 0; --i) {
  4017.         if (items[i].locationKey == KEY_APP_PROFILE ||
  4018.             items[i].locationKey == KEY_APP_GLOBAL)
  4019.           itemsToCheck.push(items[i].id);
  4020.       }
  4021.       this._finishOperations();
  4022.     }
  4023.  
  4024.     // If there are no items to migrate settings for return early.
  4025.     if (itemsToCheck.length == 0)
  4026.       return;
  4027.  
  4028.     var fileURL = getURLSpecFromFile(oldExtensionsFile);
  4029.     var oldExtensionsDS = gRDF.GetDataSourceBlocking(fileURL);
  4030.     var versionChecker = getVersionChecker();
  4031.     var ds = this.datasource;
  4032.     var currAppVersion = gApp.version;
  4033.     var currAppID = gApp.ID;
  4034.     for (var i = 0; i < itemsToCheck.length; ++i) {
  4035.       var item = ds.getItemForID(itemsToCheck[i]);
  4036.       var oldPrefix = (item.type == nsIUpdateItem.TYPE_EXTENSION) ? PREFIX_EXTENSION : PREFIX_THEME;
  4037.       var oldRes = gRDF.GetResource(oldPrefix + item.id);
  4038.       // Disable the item if it was disabled in the version 1.0 extensions
  4039.       // datasource.
  4040.       if (oldExtensionsDS.GetTarget(oldRes, EM_R("disabled"), true))
  4041.         ds.setItemProperty(item.id, EM_R("userDisabled"), EM_L("true"));
  4042.  
  4043.       // app enable all items. If it is incompatible it will be app disabled
  4044.       // later on.
  4045.       ds.setItemProperty(item.id, EM_R("appDisabled"), null);
  4046.  
  4047.       // if the item is already compatible don't attempt to migrate the
  4048.       // item's compatibility info
  4049.       var newRes = getResourceForID(itemsToCheck[i]);
  4050.       if (ds.isCompatible(ds, newRes))
  4051.         continue;
  4052.  
  4053.       var updatedMinVersion = null;
  4054.       var updatedMaxVersion = null;
  4055.       var targetApps = oldExtensionsDS.GetTargets(oldRes, EM_R("targetApplication"), true);
  4056.       while (targetApps.hasMoreElements()) {
  4057.         var targetApp = targetApps.getNext();
  4058.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  4059.           try {
  4060.             var foundAppID = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("id"), true));
  4061.             if (foundAppID != currAppID) // Different target application
  4062.               continue;
  4063.  
  4064.             updatedMinVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("minVersion"), true));
  4065.             updatedMaxVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("maxVersion"), true));
  4066.  
  4067.             // Only set the target app info if the extension's target app info
  4068.             // in the version 1.0 extensions datasource makes it compatible
  4069.             if (versionChecker.compare(currAppVersion, updatedMinVersion) >= 0 &&
  4070.                 versionChecker.compare(currAppVersion, updatedMaxVersion) <= 0)
  4071.               ds.updateTargetAppInfo(item.id, updatedMinVersion, updatedMaxVersion);
  4072.  
  4073.             break;
  4074.           }
  4075.           catch (e) { 
  4076.           }
  4077.         }
  4078.       }
  4079.     }
  4080.   },
  4081.  
  4082.   /**
  4083.    * Write the Extensions List and the Startup Cache
  4084.    * @param   needsRestart
  4085.    *          true if the application needs to restart again, false otherwise.
  4086.    */  
  4087.   _updateManifests: function(needsRestart) {
  4088.     // Write the Startup Cache (All Items, visible or not)
  4089.     StartupCache.write();
  4090.     // Write the Extensions Locations Manifest (Visible, enabled items)
  4091.     this._updateExtensionsManifest(needsRestart);
  4092.   },
  4093.  
  4094.   /**
  4095.    * Get a list of items that are currently "active" (turned on) of a specific
  4096.    * type
  4097.    * @param   type
  4098.    *          The nsIUpdateItem type to return a list of items of
  4099.    * @returns An array of active items of the specified type.
  4100.    */
  4101.   _getActiveItems: function(type) {
  4102.     var allItems = this.getItemList(type, { });
  4103.     var activeItems = [];
  4104.     var ds = this.datasource;
  4105.     for (var i = 0; i < allItems.length; ++i) {
  4106.       var item = allItems[i];
  4107.  
  4108.       // An item entry is valid only if it is not disabled, not about to 
  4109.       // be disabled, and not about to be uninstalled.
  4110.       var installLocation = this.getInstallLocation(item.id);
  4111.       if (installLocation.name in StartupCache.entries &&
  4112.           item.id in StartupCache.entries[installLocation.name] &&
  4113.           StartupCache.entries[installLocation.name][item.id]) {
  4114.         var op = StartupCache.entries[installLocation.name][item.id].op;
  4115.         if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE || 
  4116.             op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
  4117.           continue;
  4118.       }
  4119.       // Suppress items that have been disabled by the user or the app.
  4120.       if (ds.getItemProperty(item.id, "isDisabled") != "true")
  4121.         activeItems.push({ id: item.id, location: installLocation });
  4122.     }
  4123.  
  4124.     return activeItems;
  4125.   },
  4126.   
  4127.   /**
  4128.    * Write the Extensions List
  4129.    * @param   needsRestart
  4130.    *          true if the application needs to restart again, false otherwise.
  4131.    */
  4132.   _updateExtensionsManifest: function(needsRestart) {
  4133.     // When an operation is performed that requires a component re-registration
  4134.     // (extension enabled/disabled, installed, uninstalled), we must write the
  4135.     // set of paths where extensions live so that the startup system can determine
  4136.     // where additional components, preferences, chrome manifests etc live.
  4137.     //
  4138.     // To do this we obtain a list of active extensions and themes and write 
  4139.     // these to the extensions.ini file in the profile directory.
  4140.     var validExtensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION +
  4141.                                                nsIUpdateItem.TYPE_LOCALE +
  4142.                                                nsIUpdateItem.TYPE_PLUGIN);
  4143.     var validThemes     = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  4144.  
  4145.     var extensionsLocationsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  4146.     var fos = openSafeFileOutputStream(extensionsLocationsFile);
  4147.         
  4148.     var extensionSectionHeader = "[ExtensionDirs]\r\n";
  4149.     fos.write(extensionSectionHeader, extensionSectionHeader.length);
  4150.     for (var i = 0; i < validExtensions.length; ++i) {
  4151.       var e = validExtensions[i];
  4152.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  4153.       var descriptor = getAbsoluteDescriptor(itemLocation);
  4154.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  4155.       fos.write(line, line.length);
  4156.     }
  4157.  
  4158.     var themeSectionHeader = "[ThemeDirs]\r\n";
  4159.     fos.write(themeSectionHeader, themeSectionHeader.length);
  4160.     for (i = 0; i < validThemes.length; ++i) {
  4161.       var e = validThemes[i];
  4162.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  4163.       var descriptor = getAbsoluteDescriptor(itemLocation);
  4164.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  4165.       fos.write(line, line.length);
  4166.     }
  4167.  
  4168.     closeSafeFileOutputStream(fos);
  4169.  
  4170.     // Now refresh the compatibility manifest.
  4171.     this._extensionListChanged = needsRestart;
  4172.   },
  4173.   
  4174.   /**
  4175.    * Say whether or not the Extension List has changed (and thus whether or not
  4176.    * the system will have to restart the next time it is started).
  4177.    * @param   val
  4178.    *          true if the Extension List has changed, false otherwise.
  4179.    * @returns |val|
  4180.    */
  4181.   set _extensionListChanged(val) {
  4182.     // When an extension has an operation perform on it (e.g. install, upgrade,
  4183.     // disable, etc.) we are responsible for creating the .autoreg file and
  4184.     // nsAppRunner is responsible for removing it on restart. At some point it
  4185.     // may make sense to be able to cancel a registration but for now we only
  4186.     // create the file.
  4187.     try {
  4188.       var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
  4189.       if (val && !autoregFile.exists())
  4190.         autoregFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  4191.     }
  4192.     catch (e) {
  4193.     }
  4194.     return val;
  4195.   },
  4196.   
  4197.   /**
  4198.    * Gathers data about an item specified by the supplied Install Manifest
  4199.    * and determines whether or not it can be installed as-is. It makes this 
  4200.    * determination by validating the item's GUID, Version, and determining 
  4201.    * if it is compatible with this application.
  4202.    * @param   installManifest 
  4203.    *          A nsIRDFDataSource representing the Install Manifest of the 
  4204.    *          item to be installed.
  4205.    * @return  A JS Object with the following properties:
  4206.    *          "id"       The GUID of the Item being installed.
  4207.    *          "version"  The Version string of the Item being installed.
  4208.    *          "name"     The Name of the Item being installed.
  4209.    *          "type"     The nsIUpdateItem type of the Item being installed.
  4210.    *          "targetApps" An array of TargetApplication Info Objects
  4211.    *                     with "id", "minVersion" and "maxVersion" properties,
  4212.    *                     representing applications targeted by this item.
  4213.    *          "error"    The result code:
  4214.    *                     INSTALLERROR_SUCCESS      
  4215.    *                       no error, item can be installed
  4216.    *                     INSTALLERROR_INVALID_GUID 
  4217.    *                       error, GUID is not well-formed
  4218.    *                     INSTALLERROR_INVALID_VERSION
  4219.    *                       error, Version is not well-formed
  4220.    *                     INSTALLERROR_INCOMPATIBLE_VERSION
  4221.    *                       error, item is not compatible with this version
  4222.    *                       of the application.
  4223.    *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
  4224.    *                       error, item is not compatible with the operating
  4225.    *                       system or ABI the application was built for.
  4226.    *                     INSTALLERROR_BLOCKLISTED
  4227.    *                       error, item is blocklisted
  4228.    */
  4229.   _getInstallData: function(installManifest) {
  4230.     var installData = { id          : "", 
  4231.                         version     : "", 
  4232.                         name        : "", 
  4233.                         type        : 0, 
  4234.                         error       : INSTALLERROR_SUCCESS, 
  4235.                         targetApps  : [],
  4236.                         currentApp  : null };
  4237.  
  4238.     // Fetch properties from the Install Manifest
  4239.     installData.id       = getManifestProperty(installManifest, "id");
  4240.     installData.version  = getManifestProperty(installManifest, "version");
  4241.     installData.name     = getManifestProperty(installManifest, "name");
  4242.     installData.type     = getAddonTypeFromInstallManifest(installManifest);
  4243.     installData.updateURL= getManifestProperty(installManifest, "updateURL");
  4244.  
  4245.     /**
  4246.      * Reads a property off a Target Application resource
  4247.      * @param   resource
  4248.      *          The RDF Resource for a Target Application
  4249.      * @param   property
  4250.      *          The property (less EM_NS) to read
  4251.      * @returns The string literal value of the property.
  4252.      */
  4253.     function readTAProperty(resource, property) {
  4254.       return stringData(installManifest.GetTarget(resource, EM_R(property), true));
  4255.     }
  4256.     
  4257.     var targetApps = installManifest.GetTargets(gInstallManifestRoot, 
  4258.                                                 EM_R("targetApplication"), 
  4259.                                                 true);
  4260.     while (targetApps.hasMoreElements()) {
  4261.       var targetApp = targetApps.getNext();
  4262.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  4263.         try {
  4264.           var data = { id        : readTAProperty(targetApp, "id"),
  4265.                        minVersion: readTAProperty(targetApp, "minVersion"),
  4266.                        maxVersion: readTAProperty(targetApp, "maxVersion") };
  4267.           installData.targetApps.push(data);
  4268.           if (data.id == gApp.ID) 
  4269.             installData.currentApp = data;
  4270.         }
  4271.         catch (e) {
  4272.           continue;
  4273.         }
  4274.       }
  4275.     }
  4276.  
  4277.     // If the item specifies one or more target platforms, make sure our OS/ABI
  4278.     // combination is in the list - otherwise, refuse to install the item.
  4279.     var targetPlatforms = null;
  4280.     try {
  4281.       targetPlatforms = installManifest.GetTargets(gInstallManifestRoot, 
  4282.                                                    EM_R("targetPlatform"), 
  4283.                                                    true);
  4284.     } catch(e) {
  4285.       // No targetPlatform nodes, continue.
  4286.     }
  4287.     if (targetPlatforms != null && targetPlatforms.hasMoreElements()) {
  4288.       var foundMatchingOS = false;
  4289.       var foundMatchingOSAndABI = false;
  4290.       var requireABICompatibility = false;
  4291.       while (targetPlatforms.hasMoreElements()) {
  4292.         var targetPlatform = stringData(targetPlatforms.getNext());
  4293.         var os = targetPlatform.split("_")[0];
  4294.         var index = targetPlatform.indexOf("_");
  4295.         var abi = index != -1 ? targetPlatform.substr(index + 1) : null;
  4296.         if (os == gOSTarget) {
  4297.           foundMatchingOS = true;
  4298.           // The presence of any ABI part after our OS means ABI is important.
  4299.           if (abi != null) {
  4300.             requireABICompatibility = true;
  4301.             // If we don't know our ABI, we can't be compatible
  4302.             if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) {
  4303.               foundMatchingOSAndABI = true;
  4304.               break;
  4305.             }
  4306.           }
  4307.         }
  4308.       }
  4309.       if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) {
  4310.         installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM;
  4311.         return installData;
  4312.       }
  4313.     }
  4314.  
  4315.     // Validate the Item ID
  4316.     if (!gIDTest.test(installData.id)) {
  4317.       installData.error = INSTALLERROR_INVALID_GUID;
  4318.       return installData;
  4319.     }
  4320.      
  4321.     // Check the target application range specified by the extension metadata.
  4322.     if (gCheckCompatibility &&
  4323.         !this.datasource.isCompatible(installManifest, gInstallManifestRoot, undefined))
  4324.       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
  4325.     
  4326.     // Check if the item is blocklisted.
  4327.     if (this.datasource.isBlocklisted(installData.id, installData.version,
  4328.                                       undefined, undefined))
  4329.       installData.error = INSTALLERROR_BLOCKLISTED;
  4330.  
  4331.     return installData;
  4332.   },  
  4333.   
  4334.   /**
  4335.    * Installs an item from a XPI/JAR file. 
  4336.    * This is the main entry point into the Install system from outside code
  4337.    * (e.g. XPInstall).
  4338.    * @param   aXPIFile
  4339.    *          The file to install from.
  4340.    * @param   aInstallLocationKey
  4341.    *          The name of the Install Location where this item should be 
  4342.    *          installed.
  4343.    */  
  4344.   installItemFromFile: function(xpiFile, installLocationKey) {
  4345.     this.installItemFromFileInternal(xpiFile, installLocationKey, null);
  4346.   },
  4347.   
  4348.   /**
  4349.    * Installs an item from a XPI/JAR file.
  4350.    * @param   aXPIFile
  4351.    *          The file to install from.
  4352.    * @param   aInstallLocationKey
  4353.    *          The name of the Install Location where this item should be 
  4354.    *          installed.
  4355.    * @param   aInstallManifest
  4356.    *          An updated Install Manifest from the Version Update check.
  4357.    *          Can be null when invoked from callers other than the Version
  4358.    *          Update check.
  4359.    */
  4360.   installItemFromFileInternal: function(aXPIFile, aInstallLocationKey, aInstallManifest) {
  4361.     var em = this;
  4362.     /**
  4363.      * Gets the Install Location for an Item.
  4364.      * @param   itemID 
  4365.      *          The GUID of the item to find an Install Location for.
  4366.      * @return  An object implementing nsIInstallLocation which represents the 
  4367.      *          location where the specified item should be installed. 
  4368.      *          This can be:
  4369.      *          1. an object that corresponds to the location key supplied to
  4370.      *             |installItemFromFileInternal|,
  4371.      *          2. the default install location (the App Profile Extensions Folder)
  4372.      *             if no location key was supplied, or the location key supplied
  4373.      *             was not in the set of registered locations
  4374.      *          3. null, if the location selected by 1 or 2 above does not support
  4375.      *             installs from XPI/JAR files, or that location is not writable 
  4376.      *             with the current access privileges.
  4377.      */
  4378.     function getInstallLocation(itemID) {
  4379.       // Here I use "upgrade" to mean "install a different version of an item".
  4380.       var installLocation = em.getInstallLocation(itemID);
  4381.       if (!installLocation) {
  4382.         // This is not an "upgrade", since we don't have any location data for the
  4383.         // extension ID specified - that is, it's not in our database.
  4384.  
  4385.         // Caller supplied a key to a registered location, use that location
  4386.         // for the installation
  4387.         installLocation = InstallLocations.get(aInstallLocationKey);
  4388.         if (installLocation) {
  4389.           // If the specified location does not have a common metadata location
  4390.           // (e.g. extensions have no common root, or other location specified
  4391.           // by the location implementation) - e.g. for a Registry Key enumeration
  4392.           // location - we cannot install or upgrade using a XPI file, probably
  4393.           // because these application types will be handling upgrading themselves.
  4394.           // Just bail.
  4395.           if (!installLocation.location) {
  4396.             LOG("Install Location \"" + installLocation.name + "\" does not support " + 
  4397.                 "installation of items from XPI/JAR files. You must manage " + 
  4398.                 "installation and update of these items yourself.");
  4399.             installLocation = null;
  4400.           }
  4401.         }
  4402.         else {
  4403.           // In the absence of a preferred install location, just default to
  4404.           // the App-Profile 
  4405.           installLocation = InstallLocations.get(KEY_APP_PROFILE);
  4406.         }
  4407.       } 
  4408.       else {
  4409.         // This is an "upgrade", but not through the Update System, because the
  4410.         // Update code will not let an extension with an incompatible target
  4411.         // app version range through to this point. This is an "upgrade" in the
  4412.         // sense that the user found a different version of an installed extension
  4413.         // and installed it through the web interface, so we have metadata.
  4414.         
  4415.         // If the location is different, return the preferred location rather than
  4416.         // the location of the currently installed version, because we may be in
  4417.         // the situation where an item is being installed into the global app 
  4418.         // dir when there's a version in the profile dir.
  4419.         if (installLocation.name != aInstallLocationKey) 
  4420.           installLocation = InstallLocations.get(aInstallLocationKey);
  4421.       }
  4422.       if (!installLocation.canAccess) {
  4423.         LOG("Install Location\"" + installLocation.name + "\" cannot be written " +
  4424.             "to with your access privileges. Installation will not proceed.");
  4425.         installLocation = null;
  4426.       }
  4427.       return installLocation;
  4428.     }
  4429.     
  4430.     /**
  4431.      * Stages a XPI file in the default item location specified by other 
  4432.      * applications when they registered with XulRunner if the item's
  4433.      * install manifest specified compatibility with them.
  4434.      */
  4435.     function stageXPIForOtherApps(xpiFile, installData) {
  4436.       for (var i = 0; i < installData.targetApps.length; ++i) {
  4437.         var targetApp = installData.targetApps[i];
  4438.         if (targetApp.id != gApp.ID) {
  4439.         /* XXXben uncomment when this works!
  4440.           var settingsThingy = Components.classes[]
  4441.                                         .getService(Components.interfaces.nsIXULRunnerSettingsThingy);
  4442.           try {
  4443.             var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\";
  4444.             var branch = settingsThingy.getBranch(appPrefix + targetApp.id);
  4445.             var path = branch.getProperty("ExtensionsLocation");
  4446.             var destination = Components.classes["@mozilla.org/file/local;1"]
  4447.                                         .createInstance(nsILocalFile);
  4448.             destination.initWithPath(path);
  4449.             xpiFile.copyTo(file, xpiFile.leafName);
  4450.           }
  4451.           catch (e) {
  4452.           }
  4453.          */
  4454.         } 
  4455.       }        
  4456.     }
  4457.     
  4458.     /**
  4459.      * Extracts and then starts the install for extensions / themes contained
  4460.      * within a xpi.
  4461.      */
  4462.     function installMultiXPI(xpiFile, installData) {
  4463.       var fileURL = getURIFromFile(xpiFile).QueryInterface(nsIURL);
  4464.       if (fileURL.fileExtension.toLowerCase() != "xpi") {
  4465.         LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " + 
  4466.             "invalid file extension. Only xpi file extensions are allowed for " +
  4467.             "multiple item packages.");
  4468.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4469.         showMessage("invalidFileExtTitle", [], 
  4470.                     "invalidFileExtMessage", [installData.name,
  4471.                     fileURL.fileExtension,
  4472.                     bundle.GetStringFromName("type-" + installData.type)]);
  4473.         return;
  4474.       }
  4475.  
  4476.       try {
  4477.         var zipReader = getZipReaderForFile(xpiFile);
  4478.       }
  4479.       catch (e) {
  4480.         LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path);
  4481.         throw e;
  4482.       }
  4483.  
  4484.       var searchForEntries = ["*.xpi", "*.jar"];
  4485.       var files = [];
  4486.       for (var i = 0; i < searchForEntries.length; ++i) {
  4487.         var entries = zipReader.findEntries(searchForEntries[i]);
  4488.         while (entries.hasMoreElements()) {
  4489.           var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  4490.           var target = getFile(KEY_TEMPDIR, [entry.name]);
  4491.           try {
  4492.             target.createUnique(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  4493.           }
  4494.           catch (e) {
  4495.             LOG("installMultiXPI: failed to create target file for extraction " +
  4496.                 " file = " + target.path + ", exception = " + e + "\n");
  4497.           }
  4498.           zipReader.extract(entry.name, target);
  4499.           files.push(target);
  4500.         }
  4501.       }
  4502.       zipReader.close();
  4503.  
  4504.       if (files.length == 0) {
  4505.         LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " +
  4506.             "not contain a valid package to install.");
  4507.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4508.         showMessage("missingPackageFilesTitle",
  4509.                     [bundle.GetStringFromName("type-" + installData.type)],
  4510.                     "missingPackageFilesMessage", [installData.name,
  4511.                     bundle.GetStringFromName("type-" + installData.type)]);
  4512.         return;
  4513.       }
  4514.  
  4515.       for (i = 0; i < files.length; ++i) {
  4516.         em.installItemFromFileInternal(files[i], aInstallLocationKey, null);
  4517.         files[i].remove(false);
  4518.       }
  4519.     }
  4520.  
  4521.     /**
  4522.      * An observer for the Extension Update System.
  4523.      * @constructor
  4524.      */
  4525.     function IncompatibleObserver() {}
  4526.     IncompatibleObserver.prototype = {
  4527.       _id: null,
  4528.       _type: nsIUpdateItem.TYPE_ANY,
  4529.       _xpi: null,
  4530.       _installManifest: null,
  4531.       _installRDF: null,
  4532.       
  4533.       /** 
  4534.        * Ask the Extension Update System if there are any version updates for
  4535.        * this item that will allow it to be compatible with this version of 
  4536.        * the Application.
  4537.        * @param   installManifest 
  4538.        *          The Install Manifest datasource for the item.
  4539.        * @param   installData
  4540.        *          The Install Data object for the item.
  4541.        * @param   xpiFile         
  4542.        *          The staged source XPI file that contains the item. Cleaned 
  4543.        *          up by this process.
  4544.        */
  4545.       checkForUpdates: function(installManifest, installData, xpiFile, installRDF) {
  4546.         this._id              = installData.id;
  4547.         this._type            = installData.type;
  4548.         this._xpi             = xpiFile;
  4549.         this._installManifest = installManifest;
  4550.         this._installRDF      = installRDF;
  4551.         
  4552.         var item = makeItem(installData.id, installData.version, 
  4553.                             aInstallLocationKey, 
  4554.                             installData.currentApp.minVersion, 
  4555.                             installData.currentApp.maxVersion,
  4556.                             installData.name,
  4557.                             "", /* XPI Update URL */
  4558.                             "", /* XPI Update Hash */
  4559.                             "", /* Icon URL */
  4560.                             installData.updateURL || "", 
  4561.                             installData.type);
  4562.         em.update([item], 1, true, this);
  4563.       },
  4564.       
  4565.       /**
  4566.        * See nsIExtensionManager.idl
  4567.        */
  4568.       onUpdateStarted: function() {
  4569.         LOG("Phone Home Listener: Update Started");
  4570.         em.datasource.onUpdateStarted();
  4571.       },
  4572.       
  4573.       /**
  4574.        * See nsIExtensionManager.idl
  4575.        */
  4576.       onUpdateEnded: function() {
  4577.         LOG("Phone Home Listener: Update Ended");
  4578.         // We are responsible for cleaning up this file!
  4579.         this._installRDF.remove(false);
  4580.         em.datasource.onUpdateEnded();
  4581.       },
  4582.       
  4583.       /**
  4584.        * See nsIExtensionManager.idl
  4585.        */
  4586.       onAddonUpdateStarted: function(addon) {
  4587.         LOG("Phone Home Listener: Update For " + addon.id + " started");
  4588.         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
  4589.                                                 addon.type, addon.version);
  4590.         em.datasource.onAddonUpdateStarted(addon);
  4591.       },
  4592.       
  4593.       /**
  4594.        * See nsIExtensionManager.idl
  4595.        */
  4596.       onAddonUpdateEnded: function(addon, status) {
  4597.         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status); 
  4598.         em.datasource.removeDownload(this._xpi.path);
  4599.         LOG("Version Check Phone Home Completed");
  4600.         // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently
  4601.         // supported
  4602.         if (status == nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) {
  4603.           em.datasource.setTargetApplicationInfo(addon.id, 
  4604.                                                  addon.minAppVersion,
  4605.                                                  addon.maxAppVersion, 
  4606.                                                  this._installManifest);
  4607.  
  4608.           // Try and install again, but use the updated compatibility DB
  4609.           em.installItemFromFileInternal(this._xpi, aInstallLocationKey, 
  4610.                                          this._installManifest);
  4611.  
  4612.           // Add the updated compatibility info to the datasource if done
  4613.           if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) {
  4614.             em.datasource.updateTargetAppInfo(addon.id, addon.minAppVersion,
  4615.                                               addon.maxAppVersion);
  4616.           }
  4617.           else { // needs a restart
  4618.             // Add updatedMinVersion and updatedMaxVersion so it can be used
  4619.             // to update the data sources during the installation or upgrade.
  4620.             em.datasource.setUpdatedTargetAppInfo(addon.id, addon.minAppVersion,
  4621.                                                   addon.maxAppVersion);
  4622.           }
  4623.           // Prevent the datasource file from being lazily recreated after
  4624.           // it is deleted by calling Flush.
  4625.           this._installManifest.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  4626.           this._installManifest.Flush();
  4627.         }
  4628.         else {
  4629.           em.datasource.removeDownload(this._xpi.path);
  4630.           showIncompatibleError(installData);
  4631.           // We are responsible for cleaning up this file!
  4632.           InstallLocations.get(aInstallLocationKey).removeFile(this._xpi);
  4633.         }
  4634.         em.datasource.onAddonUpdateEnded(addon, status);
  4635.       },
  4636.  
  4637.       /**
  4638.        * See nsISupports.idl
  4639.        */
  4640.       QueryInterface: function(iid) {
  4641.         if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
  4642.             !iid.equals(Components.interfaces.nsISupports))
  4643.           throw Components.results.NS_ERROR_NO_INTERFACE;
  4644.         return this;
  4645.       }
  4646.     }
  4647.  
  4648.     var installManifestFile = extractRDFFileToTempDir(aXPIFile, FILE_INSTALL_MANIFEST, true);
  4649.     var shouldPhoneHomeIfNecessary = false;
  4650.     if (!aInstallManifest) {
  4651.       // If we were not called with an Install Manifest, we were called from 
  4652.       // some other path than the Phone Home system, so we do want to phone
  4653.       // home if the version is incompatible.
  4654.       shouldPhoneHomeIfNecessary = true;
  4655.       var installManifest = getInstallManifest(installManifestFile);
  4656.       if (!installManifest) {
  4657.         LOG("The Install Manifest supplied by this item is not well-formed. " + 
  4658.             "Installation will not proceed.");
  4659.         installManifestFile.remove(false);
  4660.         return;
  4661.       }
  4662.     }
  4663.     else
  4664.       installManifest = aInstallManifest;
  4665.     
  4666.     var installData = this._getInstallData(installManifest);
  4667.     switch (installData.error) {
  4668.     case INSTALLERROR_INCOMPATIBLE_VERSION:
  4669.       // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or
  4670.       // not we need it (we may need it if a remote version bump that makes it 
  4671.       // compatible is discovered by the call home) - so we must stage it for 
  4672.       // later ourselves.
  4673.       if (shouldPhoneHomeIfNecessary && installData.currentApp) {
  4674.         var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4675.         if (!installLocation) {
  4676.           installManifestFile.remove(false);
  4677.           return;
  4678.         }
  4679.         var stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4680.         (new IncompatibleObserver(this)).checkForUpdates(installManifest, 
  4681.                                                          installData, stagedFile,
  4682.                                                          installManifestFile);
  4683.         // Return early to prevent deletion of the install manifest file.
  4684.         return;
  4685.       }
  4686.       else {
  4687.         // XXXben Look up XULRunnerSettingsThingy to see if there is a registered
  4688.         //        app that can handle this item, if so just stage and don't show
  4689.         //        this error!
  4690.         showIncompatibleError(installData);
  4691.       }
  4692.       break;
  4693.     case INSTALLERROR_SUCCESS:
  4694.       // Installation of multiple extensions / themes contained within a single xpi.
  4695.       if (installData.type == nsIUpdateItem.TYPE_MULTI_XPI) {
  4696.         installMultiXPI(aXPIFile, installData);
  4697.         break;
  4698.       }
  4699.  
  4700.       // Stage the extension's XPI so it can be extracted at the next restart.
  4701.       var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4702.       if (!installLocation) {
  4703.         // No cleanup of any of the staged XPI files should be required here, 
  4704.         // because this should only ever fail on the first recurse through
  4705.         // this function, BEFORE staging takes place... technically speaking
  4706.         // a location could become readonly during the phone home process, 
  4707.         // but that's an edge case I don't care about.
  4708.         installManifestFile.remove(false);
  4709.         return;
  4710.       }
  4711.  
  4712.       // Stage a copy of the XPI/JAR file for our own evil purposes...
  4713.       stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4714.       
  4715.       var restartRequired = this.installRequiresRestart(installData.id, 
  4716.                                                         installData.type);
  4717.       // Determine which configuration function to use based on whether or not
  4718.       // there is data about this item in our datasource already - if there is 
  4719.       // we want to upgrade, otherwise we install fresh.
  4720.       var ds = this.datasource;
  4721.       if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) {
  4722.         // We enter this function if any data corresponding to an existing GUID
  4723.         // is found, regardless of its Install Location. We need to check before
  4724.         // "upgrading" an item that Install Location of the new item is of equal
  4725.         // or higher priority than the old item, to make sure the datasource only
  4726.         // ever tracks metadata for active items.
  4727.         var oldInstallLocation = this.getInstallLocation(installData.id);
  4728.         if (oldInstallLocation.priority >= installLocation.priority) {
  4729.           this._upgradeItem(installManifest, installData.id, installLocation, 
  4730.                             installData.type);
  4731.           if (!restartRequired) {
  4732.             this._finalizeUpgrade(installData.id);
  4733.             this._finalizeInstall(installData.id, stagedFile);
  4734.           }
  4735.         }
  4736.       }
  4737.       else {
  4738.         this._configureForthcomingItem(installManifest, installData.id, 
  4739.                                         installLocation, installData.type);
  4740.         if (!restartRequired)
  4741.           this._finalizeInstall(installData.id, stagedFile);
  4742.       }
  4743.       this._updateManifests(restartRequired);
  4744.       break;
  4745.     case INSTALLERROR_INVALID_GUID:
  4746.       LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" + 
  4747.           " which is not well-formed.");
  4748.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4749.       showMessage("incompatibleTitle", 
  4750.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4751.                   "invalidGUIDMessage", [installData.name, installData.id]);
  4752.       break;
  4753.     case INSTALLERROR_INVALID_VERSION:
  4754.       LOG("Invalid Version: Item: \"" + installData.id + "\" has version " + 
  4755.           installData.version + " which is not well-formed.");
  4756.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4757.       showMessage("incompatibleTitle", 
  4758.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4759.                   "invalidVersionMessage", [installData.name, installData.version]);
  4760.       break;
  4761.     case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  4762.       const osABI = gOSTarget + "_" + gXPCOMABI;
  4763.       LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " + 
  4764.           "compatible with '" + osABI + "'.");
  4765.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4766.       showMessage("incompatibleTitle", 
  4767.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4768.                   "incompatiblePlatformMessage",
  4769.                   [installData.name, BundleManager.appName, osABI]);
  4770.       break;
  4771.     case INSTALLERROR_BLOCKLISTED:
  4772.       LOG("Blocklisted Item: Item: \"" + installData.id + "\" version " + 
  4773.           installData.version + " was not installed.");
  4774.       showBlocklistMessage([installData], true);
  4775.       break;
  4776.     default:
  4777.       break;
  4778.     }
  4779.     
  4780.     // Check to see if this item supports other applications and in that case
  4781.     // stage the the XPI file in the location specified by those applications.
  4782.     stageXPIForOtherApps(aXPIFile, installData);
  4783.  
  4784.     installManifestFile.remove(false);
  4785.   },
  4786.   
  4787.   /**
  4788.    * Whether or not this type's installation/uninstallation requires 
  4789.    * the application to be restarted.
  4790.    * @param   id
  4791.    *          The GUID of the item
  4792.    * @param   type
  4793.    *          The nsIUpdateItem type of the item
  4794.    * @returns true if installation of an item of this type requires a 
  4795.    *          restart.
  4796.    */
  4797.   installRequiresRestart: function(id, type) {
  4798.     switch (type) {
  4799.     case nsIUpdateItem.TYPE_THEME:
  4800.       var internalName = this.datasource.getItemProperty(id, "internalName");
  4801.       var needsRestart = false;
  4802.       if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
  4803.         needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  4804.       if (!needsRestart &&
  4805.           gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
  4806.         needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  4807.       return needsRestart;
  4808.     }
  4809.     return true;
  4810.   },
  4811.   
  4812.   /**
  4813.    * Perform initial configuration on an item that has just or will be 
  4814.    * installed. This inserts the item into the appropriate container in the
  4815.    * datasource, so that the application UI shows the item even if it will
  4816.    * not actually be installed until the next restart.
  4817.    * @param   installManifest 
  4818.    *          The Install Manifest datasource that describes this item.
  4819.    * @param   id          
  4820.    *          The GUID of this item.
  4821.    * @param   installLocation
  4822.    *          The Install Location where this item is installed.
  4823.    * @param   type
  4824.    *          The nsIUpdateItem type of this item. 
  4825.    */  
  4826.   _configureForthcomingItem: function(installManifest, id, installLocation, type) {
  4827.     var ds = this.datasource;
  4828.     ds.updateVisibleList(id, installLocation.name, false);
  4829.     var props = { name            : EM_L(getManifestProperty(installManifest, "name")),
  4830.                   version         : EM_L(getManifestProperty(installManifest, "version")),
  4831.                   installLocation : EM_L(installLocation.name),
  4832.                   type            : EM_I(type),
  4833.                   availableUpdateURL    : null,
  4834.                   availableUpdateHash   : null,
  4835.                   availableUpdateVersion: null };
  4836.     for (var p in props)
  4837.       ds.setItemProperty(id, EM_R(p), props[p]);
  4838.     ds.updateProperty(id, "availableUpdateURL");
  4839.     
  4840.     this._setOp(id, OP_NEEDS_INSTALL);
  4841.     
  4842.     // Insert it into the child list NOW rather than later because:
  4843.     // - extensions installed using the command line need to be a member
  4844.     //   of a container during the install phase for the code to be able
  4845.     //   to identify profile vs. global
  4846.     // - extensions installed through the UI should show some kind of
  4847.     //   feedback to indicate their presence is forthcoming (i.e. they
  4848.     //   will be available after a restart).
  4849.     ds.insertItemIntoContainer(id);
  4850.     
  4851.     this._notifyAction(id, EM_ITEM_INSTALLED);
  4852.   },
  4853.   
  4854.   /**
  4855.    * Perform configuration on an item that has just or will be upgraded.
  4856.    * @param   installManifest
  4857.    *          The Install Manifest datasource that describes this item.
  4858.    * @param   itemID
  4859.    *          The GUID of this item.
  4860.    * @param   installLocation
  4861.    *          The Install Location where this item is installed.
  4862.    * @param   type
  4863.    *          The nsIUpdateItem type of this item. 
  4864.    */
  4865.   _upgradeItem: function (installManifest, id, installLocation, type) {
  4866.     // Don't change any props that would need to be reset if the install fails.
  4867.     // They will be reset as appropriate by the upgrade/install process.
  4868.     var ds = this.datasource;
  4869.     ds.updateVisibleList(id, installLocation.name, false);
  4870.     var props = { installLocation : EM_L(installLocation.name),
  4871.                   type            : EM_I(type),
  4872.                   availableUpdateURL      : null,
  4873.                   availableUpdateHash     : null,
  4874.                   availableUpdateVersion  : null };
  4875.     for (var p in props)
  4876.       ds.setItemProperty(id, EM_R(p), props[p]);
  4877.     ds.updateProperty(id, "availableUpdateURL");
  4878.  
  4879.     this._setOp(id, OP_NEEDS_UPGRADE);
  4880.     this._notifyAction(id, EM_ITEM_UPGRADED);
  4881.   },
  4882.  
  4883.   /** 
  4884.    * Completes an Extension's installation.
  4885.    * @param   id
  4886.    *          The GUID of the Extension to install.
  4887.    * @param   file
  4888.    *          The XPI/JAR file to install from. If this is null, we try to
  4889.    *          determine the stage file location from the ID.
  4890.    */
  4891.   _finalizeInstall: function(id, file) {
  4892.     var ds = this.datasource;
  4893.     var type = ds.getItemProperty(id, "type");
  4894.     if (id == 0 || id == -1) {
  4895.       ds.removeCorruptItem(id, type);
  4896.       return;
  4897.     }
  4898.     var installLocation = this.getInstallLocation(id);
  4899.     if (!installLocation) {
  4900.       // If the install location is null, that means we've reached the finalize
  4901.       // state without the item ever having metadata added for it, which implies
  4902.       // bogus data in the Startup Cache. Clear the entries and don't do anything
  4903.       // else.
  4904.       var entries = StartupCache.findEntries(id);
  4905.       for (var i = 0; i < entries.length; ++i) {
  4906.         var location = InstallLocations.get(entries[i].location);
  4907.         StartupCache.clearEntry(location, id);
  4908.         PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4909.       }
  4910.       return;
  4911.     }
  4912.     var itemLocation = installLocation.getItemLocation(id);
  4913.  
  4914.     if (!file && "stageFile" in installLocation)
  4915.       file = installLocation.getStageFile(id);
  4916.     
  4917.     // If |file| is null or does not exist, the installer assumes the item is
  4918.     // a dropped-in directory.
  4919.     var installer = new Installer(this.datasource, id, installLocation, type);
  4920.     installer.installFromFile(file);
  4921.  
  4922.     // If the file was staged, we must clean it up ourselves, otherwise the 
  4923.     // EM caller is responsible for doing so (e.g. XPInstall)
  4924.     if (file)
  4925.       installLocation.removeFile(file);
  4926.     
  4927.     // Clear the op flag from the Startup Cache and Pending Operations sets
  4928.     StartupCache.put(installLocation, id, OP_NONE, true);
  4929.     PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4930.   },
  4931.  
  4932.   /**
  4933.    * Removes an item's metadata in preparation for an upgrade-install.
  4934.    * @param   id
  4935.    *          The GUID of the item to uninstall.
  4936.    */
  4937.   _finalizeUpgrade: function(id) {
  4938.     // Retrieve the item properties *BEFORE* we clean the resource!
  4939.     var ds = this.datasource;
  4940.     var installLocation = this.getInstallLocation(id);
  4941.  
  4942.     var stagedFile = null;
  4943.     if ("getStageFile" in installLocation)
  4944.       stagedFile = installLocation.getStageFile(id);
  4945.  
  4946.     if (stagedFile)
  4947.       var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true);
  4948.     else
  4949.       installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  4950.     if (installRDF.exists()) {
  4951.       var installManifest = getInstallManifest(installRDF);
  4952.       if (installManifest) {
  4953.         var type = getAddonTypeFromInstallManifest(installManifest);
  4954.         var userDisabled = ds.getItemProperty(id, "userDisabled") == "true";
  4955.  
  4956.         // Clean the item resource
  4957.         ds.removeItemMetadata(id);
  4958.         // Now set up the properties on the item to mimic an item in its
  4959.         // "initial state" for installation.
  4960.         this._configureForthcomingItem(installManifest, id, installLocation, 
  4961.                                        type);
  4962.         if (userDisabled)
  4963.           ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  4964.       }
  4965.       if (stagedFile)
  4966.         installRDF.remove(false);
  4967.     }
  4968.     // Clear the op flag from the Pending Operations set. Do NOT clear op flag in 
  4969.     // the startup cache since this may have been reset to OP_NEEDS_INSTALL by
  4970.     // |_configureForthcomingItem|.
  4971.     PendingOperations.clearItem(OP_NEEDS_UPGRADE, id);
  4972.   },
  4973.   
  4974.   /**
  4975.    * Completes an item's uninstallation.
  4976.    * @param   id
  4977.    *          The GUID of the item to uninstall.
  4978.    */
  4979.   _finalizeUninstall: function(id) {
  4980.     var ds = this.datasource;
  4981.     
  4982.     var installLocation = this.getInstallLocation(id);
  4983.     if (!installLocation.itemIsManagedIndependently(id)) {
  4984.       try {
  4985.         // Having a callback that does nothing just causes the directory to be
  4986.         // removed.
  4987.         safeInstallOperation(id, installLocation, 
  4988.                              { data: null, callback: function() { } });
  4989.       }
  4990.       catch (e) {
  4991.         LOG("_finalizeUninstall: failed to remove directory for item: " + id + 
  4992.             " at Install Location: " + installLocation.name + ", rolling back uninstall");
  4993.         // Removal of the files failed, reset the uninstalled flag and rewrite
  4994.         // the install manifests so this item's components are registered.
  4995.         // Clear the op flag from the Startup Cache
  4996.         StartupCache.put(installLocation, id, OP_NONE, true);
  4997.         var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4998.         this._updateManifests(restartRequired);
  4999.         return;
  5000.       }
  5001.     }
  5002.     else if (installLocation.name == KEY_APP_PROFILE ||
  5003.              installLocation.name == KEY_APP_GLOBAL) {
  5004.       // Check for a pointer file and remove it if it exists
  5005.       var pointerFile = installLocation.location.clone();
  5006.       pointerFile.append(id);
  5007.       if (pointerFile.exists() && !pointerFile.isDirectory())
  5008.         pointerFile.remove(false);
  5009.     }
  5010.     
  5011.     // Clean the item resource
  5012.     ds.removeItemMetadata(id);
  5013.     
  5014.     // Do this LAST since inferences are made about an item based on
  5015.     // what container it's in.
  5016.     ds.removeItemFromContainer(id);
  5017.     
  5018.     // Clear the op flag from the Startup Cache and the Pending Operations set.
  5019.     StartupCache.clearEntry(installLocation, id);
  5020.     PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id);
  5021.   },
  5022.   
  5023.   /**
  5024.    * Uninstalls an item. If the uninstallation cannot be performed immediately
  5025.    * it is scheduled for the next restart.
  5026.    * @param   id
  5027.    *          The GUID of the item to uninstall.
  5028.    */
  5029.   uninstallItem: function(id) {
  5030.     var ds = this.datasource;
  5031.     ds.updateDownloadState(PREFIX_ITEM_URI + id, null);
  5032.     if (!ds.isDownloadItem(id)) {
  5033.       var opType = ds.getItemProperty(id, "opType");
  5034.       var installLocation = this.getInstallLocation(id);
  5035.       // Removes any staged xpis for this item.
  5036.       if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL) {
  5037.         var stageFile = installLocation.getStageFile(id);
  5038.         if (stageFile)
  5039.           installLocation.removeFile(stageFile);
  5040.       }
  5041.       // Addons with an opType of OP_NEEDS_INSTALL only have a staged xpi file
  5042.       // and are removed immediately since the uninstall can't be canceled.
  5043.       if (opType == OP_NEEDS_INSTALL) {
  5044.         ds.removeItemMetadata(id);
  5045.         ds.removeItemFromContainer(id);
  5046.         ds.updateVisibleList(id, null, true);
  5047.         StartupCache.clearEntry(installLocation, id);
  5048.         this._updateManifests(false);
  5049.       }
  5050.       else {
  5051.         this._setOp(id, OP_NEEDS_UNINSTALL);
  5052.         var type = ds.getItemProperty(id, "type");
  5053.         var restartRequired = this.installRequiresRestart(id, type);
  5054.         if (!restartRequired) {
  5055.           this._finalizeUninstall(id);
  5056.           this._updateManifests(restartRequired);
  5057.         }
  5058.       }
  5059.     }
  5060.     else {
  5061.       // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi"
  5062.       // ... just remove it from the list. 
  5063.       ds.removeCorruptDLItem(id);
  5064.     }
  5065.     
  5066.     this._notifyAction(id, EM_ITEM_UNINSTALLED);
  5067.   },
  5068.  
  5069.   /**
  5070.    * Cancels a pending uninstall of an item
  5071.    * @param   id
  5072.    *          The ID of the item.
  5073.    */
  5074.   cancelUninstallItem: function(id) {
  5075.     var ds = this.datasource;
  5076.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5077.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5078.     if (appDisabled == "true" || appDisabled == OP_NONE && userDisabled == OP_NONE) {
  5079.       this._setOp(id, OP_NONE);
  5080.       this._notifyAction(id, EM_ITEM_CANCEL);
  5081.     }
  5082.     else if (appDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NEEDS_DISABLE) {
  5083.       this._setOp(id, OP_NEEDS_DISABLE);
  5084.       this._notifyAction(id, EM_ITEM_DISABLED);
  5085.     }
  5086.     else if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE) {
  5087.       this._setOp(id, OP_NEEDS_ENABLE);
  5088.       this._notifyAction(id, EM_ITEM_ENABLED);
  5089.     }
  5090.     else {
  5091.       this._setOp(id, OP_NONE);
  5092.       this._notifyAction(id, EM_ITEM_CANCEL);
  5093.     }
  5094.   },
  5095.  
  5096.   /**
  5097.    * Sets the pending operation for a visible item. 
  5098.    * @param   id
  5099.    *          The GUID of the item
  5100.    * @param   op
  5101.    *          The name of the operation to be performed
  5102.    */  
  5103.   _setOp: function(id, op) {
  5104.     var location = this.getInstallLocation(id);
  5105.     StartupCache.put(location, id, op, true);
  5106.     PendingOperations.addItem(op, { locationKey: location.name, id: id });
  5107.     var ds = this.datasource;
  5108.     if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE)
  5109.       ds.updateDownloadState(PREFIX_ITEM_URI + id, "success");
  5110.  
  5111.     ds.updateProperty(id, "opType");
  5112.     ds.updateProperty(id, "updateable");
  5113.     ds.updateProperty(id, "satisfiesDependencies");
  5114.     var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  5115.     this._updateDependentItemsForID(id);
  5116.     this._updateManifests(restartRequired);
  5117.   },
  5118.   
  5119.   /**
  5120.    * Note on appDisabled and userDisabled property arcs.
  5121.    * The appDisabled and userDisabled RDF property arcs are used to store
  5122.    * the pending operation for app disabling and user disabling for an item as
  5123.    * well as the user and app disabled status after the pending operation has
  5124.    * been completed upon restart. When the appDisabled value changes the value
  5125.    * of userDisabled is reset to prevent the state of widgets and status
  5126.    * messages from being in an incorrect state.
  5127.    */
  5128.  
  5129.   /**
  5130.    * Enables an item for the application (e.g. the item satisfies all
  5131.    * requirements like app compatibility for it to be enabled). The appDisabled
  5132.    * property arc will be removed if the item will be app disabled on next
  5133.    * restart to cancel the app disabled operation for the item otherwise the
  5134.    * property value will be set to OP_NEEDS_ENABLE. The item's pending
  5135.    * operations are then evaluated in order to set the operation to perform
  5136.    * and notify the observers if the operation has been changed.
  5137.    * See "Note on appDisabled and userDisabled property arcs" above.
  5138.    * @param   id
  5139.    *          The ID of the item to be enabled by the application.
  5140.    */
  5141.   _appEnableItem: function(id) {
  5142.     var ds = this.datasource;
  5143.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5144.     if (appDisabled == OP_NONE || appDisabled == OP_NEEDS_ENABLE)
  5145.       return;
  5146.  
  5147.     var opType = ds.getItemProperty(id, "opType");
  5148.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5149.     // reset user disabled if it has a pending operation to prevent the ui
  5150.     // state from getting confused as to an item's current state.
  5151.     if (userDisabled == OP_NEEDS_DISABLE)
  5152.       ds.setItemProperty(id, EM_R("userDisabled"), null);
  5153.     else if (userDisabled == OP_NEEDS_ENABLE)
  5154.       ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5155.  
  5156.     if (appDisabled == OP_NEEDS_DISABLE)
  5157.       ds.setItemProperty(id, EM_R("appDisabled"), null);
  5158.     else if (appDisabled == "true")
  5159.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_ENABLE));
  5160.  
  5161.     // Don't set a new operation when there is a pending uninstall operation.
  5162.     if (opType == OP_NEEDS_UNINSTALL) {
  5163.       this._updateDependentItemsForID(id);
  5164.       return;
  5165.     }
  5166.  
  5167.     var operation, action;
  5168.     // if this item is already enabled or user disabled don't set a pending
  5169.     // operation - instead immediately enable it and reset the operation type
  5170.     // if needed.
  5171.     if (appDisabled == OP_NEEDS_DISABLE || appDisabled == OP_NONE ||
  5172.         userDisabled == "true") {
  5173.       if (opType != OP_NONE) {
  5174.         operation = OP_NONE;
  5175.         action = EM_ITEM_CANCEL;
  5176.       }
  5177.     }
  5178.     else {
  5179.       if (opType != OP_NEEDS_ENABLE) {
  5180.         operation = OP_NEEDS_ENABLE;
  5181.         action = EM_ITEM_ENABLED;
  5182.       }
  5183.     }
  5184.  
  5185.     if (action) {
  5186.       this._setOp(id, operation);
  5187.       this._notifyAction(id, action);
  5188.     }
  5189.     else {
  5190.       ds.updateProperty(id, "satisfiesDependencies");
  5191.       this._updateDependentItemsForID(id);
  5192.     }
  5193.   },
  5194.  
  5195.   /**
  5196.    * Disables an item for the application (e.g. the item doesn't satisfy all
  5197.    * requirements like app compatibility for it to be enabled). The appDisabled
  5198.    * property arc will be set to true if the item will be app enabled on next
  5199.    * restart to cancel the app enabled operation for the item otherwise the
  5200.    * property value will be set to OP_NEEDS_DISABLE. The item's pending
  5201.    * operations are then evaluated in order to set the operation to perform
  5202.    * and notify the observers if the operation has been changed.
  5203.    * See "Note on appDisabled and userDisabled property arcs" above.
  5204.    * @param   id
  5205.    *          The ID of the item to be disabled by the application.
  5206.    */
  5207.   _appDisableItem: function(id) {
  5208.     var ds = this.datasource;
  5209.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5210.     if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE)
  5211.       return;
  5212.  
  5213.     var opType = ds.getItemProperty(id, "opType");
  5214.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5215.  
  5216.     // reset user disabled if it has a pending operation to prevent the ui
  5217.     // state from getting confused as to an item's current state.
  5218.     if (userDisabled == OP_NEEDS_DISABLE)
  5219.       ds.setItemProperty(id, EM_R("userDisabled"), null);
  5220.     else if (userDisabled == OP_NEEDS_ENABLE)
  5221.       ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5222.  
  5223.     if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE ||
  5224.         ds.getItemProperty(id, "userDisabled") == "true")
  5225.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  5226.     else if (appDisabled == OP_NONE)
  5227.       ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_DISABLE));
  5228.  
  5229.     // Don't set a new operation when there is a pending uninstall operation.
  5230.     if (opType == OP_NEEDS_UNINSTALL) {
  5231.       this._updateDependentItemsForID(id);
  5232.       return;
  5233.     }
  5234.  
  5235.     var operation, action;
  5236.     // if this item is already disabled don't set a pending operation - instead
  5237.     // immediately disable it and reset the operation type if needed.
  5238.     if (appDisabled == OP_NEEDS_ENABLE || appDisabled == "true" ||
  5239.         userDisabled == OP_NEEDS_ENABLE || userDisabled == "true") {
  5240.       if (opType != OP_NONE) {
  5241.         operation = OP_NONE;
  5242.         action = EM_ITEM_CANCEL;
  5243.       }
  5244.     }
  5245.     else {
  5246.       if (opType != OP_NEEDS_DISABLE) {
  5247.         operation = OP_NEEDS_DISABLE;
  5248.         action = EM_ITEM_DISABLED;
  5249.       }
  5250.     }
  5251.  
  5252.     if (action) {
  5253.       this._setOp(id, operation);
  5254.       this._notifyAction(id, action);
  5255.     }
  5256.     else {
  5257.       ds.updateProperty(id, "satisfiesDependencies");
  5258.       this._updateDependentItemsForID(id);
  5259.     }
  5260.   },
  5261.     
  5262.   /**
  5263.    * Sets an item to be enabled by the user. If the item is already enabled this
  5264.    * clears the needs-enable operation for the next restart.
  5265.    * See "Note on appDisabled and userDisabled property arcs" above.
  5266.    * @param   id
  5267.    *          The ID of the item to be enabled by the user.
  5268.    */
  5269.   enableItem: function(id) {
  5270.     var ds = this.datasource;
  5271.     var opType = ds.getItemProperty(id, "opType");
  5272.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5273.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5274.  
  5275.     var operation, action;
  5276.     // if this item is already enabled don't set a pending operation - instead
  5277.     // immediately enable it and reset the operation type if needed.
  5278.     if (appDisabled == OP_NONE &&
  5279.         userDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NONE) {
  5280.       if (userDisabled == OP_NEEDS_DISABLE)
  5281.         ds.setItemProperty(id, EM_R("userDisabled"), null);
  5282.       if (opType != OP_NONE) {
  5283.         operation = OP_NONE;
  5284.         action = EM_ITEM_CANCEL;
  5285.       }
  5286.     }
  5287.     else {
  5288.       if (userDisabled == "true")
  5289.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_ENABLE));
  5290.       if (opType != OP_NEEDS_ENABLE) {
  5291.         operation = OP_NEEDS_ENABLE;
  5292.         action = EM_ITEM_ENABLED;
  5293.       }
  5294.     }
  5295.  
  5296.     if (action) {
  5297.       this._setOp(id, operation);
  5298.       this._notifyAction(id, action);
  5299.     }
  5300.     else {
  5301.       ds.updateProperty(id, "satisfiesDependencies");
  5302.       this._updateDependentItemsForID(id);
  5303.     }
  5304.   },
  5305.   
  5306.   /**
  5307.    * Sets an item to be disabled by the user. If the item is already disabled
  5308.    * this clears the needs-disable operation for the next restart.
  5309.    * See "Note on appDisabled and userDisabled property arcs" above.
  5310.    * @param   id
  5311.    *          The ID of the item to be disabled by the user.
  5312.    */
  5313.   disableItem: function(id) {
  5314.     var ds = this.datasource;
  5315.     var opType = ds.getItemProperty(id, "opType");
  5316.     var appDisabled = ds.getItemProperty(id, "appDisabled");
  5317.     var userDisabled = ds.getItemProperty(id, "userDisabled");
  5318.  
  5319.     var operation, action;
  5320.     // if this item is already disabled don't set a pending operation - instead
  5321.     // immediately disable it and reset the operation type if needed.
  5322.     if (userDisabled == OP_NEEDS_ENABLE || userDisabled == "true" ||
  5323.         appDisabled == OP_NEEDS_ENABLE) {
  5324.       if (userDisabled != "true")
  5325.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  5326.       if (opType != OP_NONE) {
  5327.         operation = OP_NONE;
  5328.         action = EM_ITEM_CANCEL;
  5329.       }
  5330.     }
  5331.     else {
  5332.       if (userDisabled == OP_NONE)
  5333.         ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_DISABLE));
  5334.       if (opType != OP_NEEDS_DISABLE) {
  5335.         operation = OP_NEEDS_DISABLE;
  5336.         action = EM_ITEM_DISABLED;
  5337.       }
  5338.     }
  5339.  
  5340.     if (action) {
  5341.       this._setOp(id, operation);
  5342.       this._notifyAction(id, action);
  5343.     }
  5344.     else {
  5345.       ds.updateProperty(id, "satisfiesDependencies");
  5346.       this._updateDependentItemsForID(id);
  5347.     }
  5348.   },
  5349.   
  5350.   /**
  5351.    * Determines whether an item should be disabled by the application.
  5352.    * @param   id
  5353.    *          The ID of the item to check
  5354.    */
  5355.   _isUsableItem: function(id) {
  5356.     var ds = this.datasource;
  5357.     return ((!gCheckCompatibility || ds.getItemProperty(id, "compatible") == "true") &&
  5358.             ds.getItemProperty(id, "blocklisted") == "false" &&
  5359.             ds.getItemProperty(id, "satisfiesDependencies") == "true");
  5360.   },
  5361.  
  5362.   /**
  5363.    * Sets an item's dependent items disabled state for the app based on whether
  5364.    * its dependencies are met and the item is compatible.
  5365.    * @param   id
  5366.    *          The ID of the item whose dependent items will be checked
  5367.    */
  5368.   _updateDependentItemsForID: function(id) {
  5369.     var ds = this.datasource;
  5370.     var dependentItems = this.getDependentItemListForID(id, true, { });
  5371.     for (var i = 0; i < dependentItems.length; ++i) {
  5372.       var dependentID = dependentItems[i].id;
  5373.       ds.updateProperty(dependentID, "satisfiesDependencies");
  5374.       if (this._isUsableItem(dependentID))
  5375.         this._appEnableItem(dependentID);
  5376.       else
  5377.         this._appDisableItem(dependentID);
  5378.     }
  5379.   },
  5380.  
  5381.   /**
  5382.    * Notify observers of a change to an item that has been requested by the
  5383.    * user. 
  5384.    */
  5385.   _notifyAction: function(id, reason) {
  5386.     gOS.notifyObservers(this.datasource.getItemForID(id), 
  5387.                         EM_ACTION_REQUESTED_TOPIC, reason);
  5388.   },
  5389.   
  5390.   /**
  5391.    * See nsIExtensionManager.idl
  5392.    */
  5393.   update: function(items, itemCount, versionUpdateOnly, listener) {
  5394.     var appID = gApp.ID;
  5395.     var appVersion = gApp.version;
  5396.  
  5397.     if (items.length == 0)
  5398.       items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  5399.  
  5400.     var updater = new ExtensionItemUpdater(appID, appVersion, this);
  5401.     updater.checkForUpdates(items, items.length, versionUpdateOnly, listener);
  5402.   },
  5403.  
  5404.  
  5405.   /**
  5406.    * Checks for changes to the blocklist using the local blocklist file,
  5407.    * application disables / enables items that have been added / removed from
  5408.    * the blocklist, and if there are additions to the blocklist this will
  5409.    * inform the user by displaying a list of the items added.
  5410.    *
  5411.    * XXXrstrong - this method is not terribly useful and was added so we can
  5412.    * trigger this check from the additional timer used by blocklisting.
  5413.    */
  5414.   checkForBlocklistChanges: function() {
  5415.     var ds = this.datasource;
  5416.     var items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  5417.     for (var i = 0; i < items.length; ++i) {
  5418.       var id = items[i].id;
  5419.       ds.updateProperty(id, "blocklisted");
  5420.       if (this._isUsableItem(id))
  5421.         this._appEnableItem(id);
  5422.     }
  5423.  
  5424.     items = ds.getBlocklistedItemList(null, null, nsIUpdateItem.TYPE_ADDON,
  5425.                                       false);
  5426.     for (i = 0; i < items.length; ++i)
  5427.       this._appDisableItem(items[i].id);
  5428.  
  5429.     // show the blocklist notification window if there are new blocklist items.
  5430.     if (items.length > 0)
  5431.       showBlocklistMessage(items, false);
  5432.   },
  5433.  
  5434.   /**
  5435.    * @returns An enumeration of all registered Install Locations.
  5436.    */
  5437.   get installLocations () {
  5438.     return InstallLocations.enumeration;
  5439.   },
  5440.   
  5441.   /**
  5442.    * Gets the Install Location where a visible Item is stored.
  5443.    * @param   id
  5444.    *          The GUID of the item to locate an Install Location for.
  5445.    * @returns The Install Location object where the item is stored.
  5446.    */
  5447.   getInstallLocation: function(id) {
  5448.     var key = this.datasource.visibleItems[id];
  5449.     return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null;
  5450.   },
  5451.   
  5452.   /**
  5453.    * Gets a nsIUpdateItem for the item with the specified id.
  5454.    * @param   id
  5455.    *          The GUID of the item to construct a nsIUpdateItem for.
  5456.    * @returns The nsIUpdateItem representing the item.
  5457.    */
  5458.   getItemForID: function(id) {
  5459.     return this.datasource.getItemForID(id);
  5460.   },
  5461.   
  5462.   /**
  5463.    * Retrieves a list of installed nsIUpdateItems of items that are dependent
  5464.    * on another item.
  5465.    * @param   id
  5466.    *          The ID of the item that other items depend on.
  5467.    * @param   includeDisabled
  5468.    *          Whether to include disabled items in the set returned.
  5469.    * @param   countRef
  5470.    *          The XPCJS reference to the number of items returned.
  5471.    * @returns An array of installed nsIUpdateItems that depend on the item
  5472.    *          specified by the id parameter.
  5473.    */
  5474.   getDependentItemListForID: function(id, includeDisabled, countRef) {
  5475.     return this.datasource.getDependentItemListForID(id, includeDisabled, countRef);
  5476.   },
  5477.  
  5478.   /**
  5479.    * Retrieves a list of nsIUpdateItems of items matching the specified type.
  5480.    * @param   type
  5481.    *          The type of item to return.
  5482.    * @param   countRef
  5483.    *          The XPCJS reference to the number of items returned.
  5484.    * @returns An array of nsIUpdateItems matching the id/type filter.
  5485.    */
  5486.   getItemList: function(type, countRef) {
  5487.     return this.datasource.getItemList(type, countRef);
  5488.   },
  5489.  
  5490.   /**  
  5491.    * See nsIExtensionManager.idl
  5492.    */
  5493.   getIncompatibleItemList: function(id, version, type, includeDisabled, 
  5494.                                     countRef) {
  5495.     var items = this.datasource.getIncompatibleItemList(id, version ? version : undefined,
  5496.                                                         type, includeDisabled);
  5497.     countRef.value = items.length;
  5498.     return items;
  5499.   },
  5500.   
  5501.   /**
  5502.    * Move an Item to the index of another item in its container.
  5503.    * @param   movingID
  5504.    *          The ID of the item to be moved.
  5505.    * @param   destinationID
  5506.    *          The ID of an item to move another item to.
  5507.    */
  5508.   moveToIndexOf: function(movingID, destinationID) {
  5509.     this.datasource.moveToIndexOf(movingID, destinationID);
  5510.   },
  5511.  
  5512.   /**
  5513.    * Sorts addons of the specified type by the specified property starting from
  5514.    * the top of their container. If the addons are already sorted then no action
  5515.    * is performed.
  5516.    * @param   type
  5517.    *          The nsIUpdateItem type of the items to sort.
  5518.    * @param   propertyName
  5519.    *          The RDF property name used for sorting.
  5520.    * @param   isAscending
  5521.    *          true to sort ascending and false to sort descending
  5522.    */
  5523.   sortTypeByProperty: function(type, propertyName, isAscending) {
  5524.     this.datasource.sortTypeByProperty(type, propertyName, isAscending);
  5525.   },
  5526.  
  5527.   /////////////////////////////////////////////////////////////////////////////    
  5528.   // Downloads
  5529.   _transactions: [],
  5530.   _downloadCount: 0,
  5531.   
  5532.   /**
  5533.    * Ask the user if they really want to quit the application, since this will 
  5534.    * cancel one or more Extension/Theme downloads.
  5535.    * @param   subject
  5536.    *          A nsISupportsPRBool which this function sets to false if the user
  5537.    *          wishes to cancel all active downloads and quit the application,
  5538.    *          false otherwise.
  5539.    */
  5540.   _confirmCancelDownloadsOnQuit: function(subject) {
  5541.     if (this._downloadCount > 0) {
  5542.       // The observers will be notified again after this so set the download
  5543.       // count to 0 to prevent this dialog from being displayed again.
  5544.       this._downloadCount = 0;
  5545.       var result;
  5546. //@line 5472 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  5547.       result = this._confirmCancelDownloads(this._downloadCount, 
  5548.                                             "quitCancelDownloadsAlertTitle",
  5549.                                             "quitCancelDownloadsAlertMsgMultiple",
  5550.                                             "quitCancelDownloadsAlertMsg",
  5551.                                             "dontQuitButtonWin");
  5552. //@line 5484 "/cygdrive/K/tinderbuild/src/flock/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  5553.       if (!result)
  5554.         this._cancelDownloads();
  5555.       if (subject instanceof Components.interfaces.nsISupportsPRBool)
  5556.         subject.data = result;
  5557.     }
  5558.   },
  5559.   
  5560.   /**
  5561.    * Ask the user if they really want to go offline, since this will cancel 
  5562.    * one or more Extension/Theme downloads.
  5563.    * @param   subject
  5564.    *          A nsISupportsPRBool which this function sets to false if the user
  5565.    *          wishes to cancel all active downloads and go offline, false
  5566.    *          otherwise.
  5567.    */
  5568.   _confirmCancelDownloadsOnOffline: function(subject) {
  5569.     if (this._downloadCount > 0) {
  5570.       result = this._confirmCancelDownloads(this._downloadCount,
  5571.                                             "offlineCancelDownloadsAlertTitle",
  5572.                                             "offlineCancelDownloadsAlertMsgMultiple",
  5573.                                             "offlineCancelDownloadsAlertMsg",
  5574.                                             "dontGoOfflineButton");
  5575.       if (!result)
  5576.         this._cancelDownloads();
  5577.       if (subject instanceof Components.interfaces.nsISupportsPRBool)
  5578.         subject.data = result;
  5579.     }
  5580.   },
  5581.   
  5582.   /**
  5583.    * Cancels all active downloads and removes them from the applicable UI.
  5584.    */
  5585.   _cancelDownloads: function() {
  5586.     for (var i = 0; i < this._transactions.length; ++i)
  5587.       gOS.notifyObservers(this._transactions[i], "xpinstall-progress", "cancel");
  5588.  
  5589.     this._removeAllDownloads();
  5590.   },
  5591.  
  5592.   /**
  5593.    * Ask the user whether or not they wish to cancel the Extension/Theme
  5594.    * downloads which are currently under way.
  5595.    * @param   count
  5596.    *          The number of active downloads.
  5597.    * @param   title
  5598.    *          The key of the title for the message box to be displayed
  5599.    * @param   cancelMessageMultiple
  5600.    *          The key of the message to be displayed in the message box
  5601.    *          when there are > 1 active downloads.
  5602.    * @param   cancelMessageSingle
  5603.    *          The key of the message to be displayed in the message box
  5604.    *          when there is just one active download.
  5605.    * @param   dontCancelButton
  5606.    *          The key of the label to be displayed on the "Don't Cancel 
  5607.    *          Downloads" button.
  5608.    */
  5609.   _confirmCancelDownloads: function(count, title, cancelMessageMultiple, 
  5610.                                     cancelMessageSingle, dontCancelButton) {
  5611.     var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES);
  5612.     var title = bundle.GetStringFromName(title);
  5613.     var message, quitButton;
  5614.     if (count > 1) {
  5615.       message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1);
  5616.       quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1);
  5617.     }
  5618.     else {
  5619.       message = bundle.GetStringFromName(cancelMessageSingle);
  5620.       quitButton = bundle.GetStringFromName("cancelDownloadsOKText");
  5621.     }
  5622.     var dontQuitButton = bundle.GetStringFromName(dontCancelButton);
  5623.     
  5624.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  5625.                        .getService(Components.interfaces.nsIWindowMediator);
  5626.     var win = wm.getMostRecentWindow("Extension:Manager");
  5627.     const nsIPromptService = Components.interfaces.nsIPromptService;
  5628.     var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  5629.                        .getService(nsIPromptService);
  5630.     var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) +
  5631.                 (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1);
  5632.     var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { });
  5633.     return rv == 1;
  5634.   },
  5635.   
  5636.   /** 
  5637.    * Adds a set of Item Downloads to the Manager and starts the download
  5638.    * operation.
  5639.    * @param   items
  5640.    *          An array of nsIUpdateItems to begin downlading.
  5641.    * @param   itemCount
  5642.    *          The length of |items|
  5643.    * @param   fromChrome
  5644.    *          true when called from chrome
  5645.    *          false when not called from chrome (e.g. web page)
  5646.    */
  5647.   addDownloads: function(items, itemCount, fromChrome) { 
  5648.     var ds = this.datasource;
  5649.     // Add observers only if they aren't already added for an active download
  5650.     if (this._downloadCount == 0) {
  5651.       gOS.addObserver(this, "offline-requested", false);
  5652.       gOS.addObserver(this, "quit-application-requested", false);
  5653.     }
  5654.     this._downloadCount += itemCount;
  5655.     
  5656.     var urls = [];
  5657.     var hashes = [];
  5658.     var txn = new ItemDownloadTransaction(this);
  5659.     for (var i = 0; i < itemCount; ++i) {
  5660.       var currItem = items[i];
  5661.       var txnID = Math.round(Math.random() * 100);
  5662.       txn.addDownload(currItem, txnID);
  5663.       this._transactions.push(txn);
  5664.       urls.push(currItem.xpiURL);
  5665.       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
  5666.       // if this is an update remove the update metadata to prevent it from
  5667.       // being updated during an install.
  5668.       if (fromChrome) {
  5669.         var id = currItem.id
  5670.         ds.setItemProperty(id, EM_R("availableUpdateURL"), null);
  5671.         ds.setItemProperty(id, EM_R("availableUpdateHash"), null);
  5672.         ds.setItemProperty(id, EM_R("availableUpdateVersion"), null);
  5673.         ds.updateProperty(id, "availableUpdateURL");
  5674.         ds.updateProperty(id, "updateable"); 
  5675.       }
  5676.       var id = fromChrome ? PREFIX_ITEM_URI + currItem.id : currItem.xpiURL;
  5677.       ds.updateDownloadState(id, "waiting");
  5678.     }
  5679.     
  5680.     if (fromChrome) {
  5681.       // Initiate an install from chrome
  5682.       var xpimgr = 
  5683.           Components.classes["@mozilla.org/xpinstall/install-manager;1"].
  5684.           createInstance(Components.interfaces.nsIXPInstallManager);
  5685.       xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn);
  5686.     }
  5687.     else
  5688.       gOS.notifyObservers(txn, "xpinstall-progress", "open");
  5689.   },
  5690.   
  5691.   /**
  5692.    * Removes a download of a URL.
  5693.    * @param   url
  5694.    *          The URL of the item being downloaded to remove.
  5695.    */
  5696.   removeDownload: function(url) {
  5697.     for (var i = 0; i < this._transactions.length; ++i) {
  5698.       if (this._transactions[i].containsURL(url)) {
  5699.         this._transactions[i].removeDownload(url);
  5700.         return;
  5701.       }
  5702.     } 
  5703.   },
  5704.   
  5705.   /**
  5706.    * Remove all downloads from all transactions.
  5707.    */
  5708.   _removeAllDownloads: function() {
  5709.     for (var i = 0; i < this._transactions.length; ++i)
  5710.       this._transactions[i].removeAllDownloads();
  5711.   },
  5712.  
  5713.   /**
  5714.    * Download Operation State has changed from one to another. 
  5715.    * 
  5716.    * The nsIXPIProgressDialog implementation in the download transaction object
  5717.    * forwards notifications through these methods which we then pass on to any
  5718.    * front end objects implementing nsIExtensionDownloadListener that 
  5719.    * are listening. We maintain the master state of download operations HERE, 
  5720.    * not in the front end, because if the user closes the extension or theme 
  5721.    * managers during the downloads we need to maintain state and not terminate
  5722.    * the download/install process. 
  5723.    *
  5724.    * @param   transaction
  5725.    *          The ItemDownloadTransaction object receiving the download 
  5726.    *          notifications from XPInstall.
  5727.    * @param   addon
  5728.    *          An object representing nsIUpdateItem for the addon being updated
  5729.    * @param   state
  5730.    *          The state we are entering
  5731.    * @param   value
  5732.    *          ???
  5733.    */
  5734.   onStateChange: function(transaction, addon, state, value) {
  5735.     for (var i = 0; i < this._updateListeners.length; ++i)
  5736.       this._updateListeners[i].onStateChange(addon, state, value);
  5737.     var ds = this.datasource;
  5738.     var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL;
  5739.     const nsIXPIProgressDialog = Components.interfaces.nsIXPIProgressDialog;
  5740.     switch (state) {
  5741.     case nsIXPIProgressDialog.DOWNLOAD_START:
  5742.       ds.updateDownloadState(id, "downloading");
  5743.       break;
  5744.     case nsIXPIProgressDialog.INSTALL_START:
  5745.       ds.updateDownloadState(id, "finishing");
  5746.       ds.updateDownloadProgress(id, null);
  5747.       break;
  5748.     case nsIXPIProgressDialog.INSTALL_DONE:
  5749.       --this._downloadCount;
  5750.       // From nsInstall.h
  5751.       // SUCCESS        = 0
  5752.       // REBOOT_NEEDED  = 999
  5753.       // USER_CANCELLED = -210
  5754.       if (value != 0 && value != 999 && value != -210 && id != addon.xpiURL) {
  5755.         ds.updateDownloadState(id, "failure");
  5756.         ds.updateDownloadProgress(id, null);
  5757.       }
  5758.       this.removeDownload(addon.xpiURL);
  5759.       break;
  5760.     case nsIXPIProgressDialog.DIALOG_CLOSE:
  5761.       for (var i = 0; i < this._transactions.length; ++i) {
  5762.         if (this._transactions[i].id == transaction.id) {
  5763.           this._transactions.splice(i, 1);
  5764.           delete transaction;
  5765.           // Remove the observers when all transactions have completed.
  5766.           if (this._transactions.length == 0) {
  5767.             gOS.removeObserver(this, "offline-requested");
  5768.             gOS.removeObserver(this, "quit-application-requested");
  5769.           }
  5770.           break;
  5771.         }
  5772.       }
  5773.       break;
  5774.     }
  5775.   },
  5776.   
  5777.   onProgress: function(addon, value, maxValue) {
  5778.     for (var i = 0; i < this._updateListeners.length; ++i)
  5779.       this._updateListeners[i].onProgress(addon, value, maxValue);
  5780.     
  5781.     var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL;
  5782.     var progress = Math.round((value / maxValue) * 100);
  5783.     this.datasource.updateDownloadProgress(id, progress);
  5784.   },
  5785.  
  5786.   _updateListeners: [],
  5787.   addUpdateListener: function(listener) {
  5788.     for (var i = 0; i < this._updateListeners.length; ++i) {
  5789.       if (this._updateListeners[i] == listener)
  5790.         return i;
  5791.     }
  5792.     this._updateListeners.push(listener);
  5793.     return this._updateListeners.length - 1;
  5794.   },
  5795.   
  5796.   removeUpdateListenerAt: function(index) {
  5797.     this._updateListeners.splice(index, 1);
  5798.   },
  5799.  
  5800.   /**
  5801.    * The Extensions RDF Datasource
  5802.    */
  5803.   _ds: null,
  5804.  
  5805.   /** 
  5806.    * Loads the Extensions Datasource. This should not be called unless: 
  5807.    * - a piece of Extensions UI is being shown, or
  5808.    * - on startup and there has been a change to an Install Location
  5809.    * ... it should NOT be called on every startup!
  5810.    */
  5811.   _ensureDS: function() {
  5812.     if (!this._ds) {
  5813.       this._ds = new ExtensionsDataSource(this);
  5814.       if (this._ds)
  5815.         this._ds.loadExtensions();
  5816.     }
  5817.   },
  5818.  
  5819.   /**
  5820.    * See nsIExtensionManager.idl
  5821.    */
  5822.   get datasource() {
  5823.     this._ensureDS();
  5824.     return this._ds.QueryInterface(Components.interfaces.nsIRDFDataSource);
  5825.   },
  5826.   
  5827.   /**
  5828.    * See nsIClassInfo.idl
  5829.    */
  5830.   getInterfaces: function(count) {
  5831.     var interfaces = [Components.interfaces.nsIExtensionManager,
  5832.                       Components.interfaces.nsIXPIProgressDialog,
  5833.                       Components.interfaces.nsIObserver];
  5834.     count.value = interfaces.length;
  5835.     return interfaces;
  5836.   },
  5837.   getHelperForLanguage: function(language) { 
  5838.     return null;
  5839.   },
  5840.   get contractID() {
  5841.     return "@mozilla.org/extensions/manager;1";
  5842.   },
  5843.   get classDescription() {
  5844.     return "Extension Manager";
  5845.   },
  5846.   get classID() {
  5847.     return Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}");
  5848.   },
  5849.   get implementationLanguage() {
  5850.     return Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  5851.   },
  5852.   get flags() {
  5853.     return Components.interfaces.nsIClassInfo.SINGLETON;
  5854.   },
  5855.  
  5856.   /**
  5857.    * See nsISupports.idl
  5858.    */
  5859.   QueryInterface: function(iid) {
  5860.     if (!iid.equals(Components.interfaces.nsIExtensionManager) &&
  5861.         !iid.equals(Components.interfaces.nsIExtensionManager_MOZILLA_1_8_BRANCH) &&
  5862.         !iid.equals(Components.interfaces.nsITimerCallback) &&
  5863.         !iid.equals(Components.interfaces.nsIObserver) &&
  5864.         !iid.equals(Components.interfaces.nsISupports))
  5865.       throw Components.results.NS_ERROR_NO_INTERFACE;
  5866.     return this;
  5867.   }
  5868. };
  5869.  
  5870. /**
  5871.  * This object implements nsIXPIProgressDialog and represents a collection of
  5872.  * XPI/JAR download and install operations. There is one 
  5873.  * ItemDownloadTransaction per back-end XPInstallManager object. We maintain
  5874.  * a collection of separate transaction objects because it's possible to have
  5875.  * multiple separate XPInstall download/install operations going on 
  5876.  * simultaneously, each with its own XPInstallManager instance. For instance
  5877.  * you could start downloading two extensions and then download a theme. Each
  5878.  * of these operations would open the appropriate FE and have to be able to
  5879.  * track each operation independently.
  5880.  * 
  5881.  * @constructor
  5882.  */
  5883. function ItemDownloadTransaction(manager) {
  5884.   this._manager = manager;
  5885.   this._downloads = [];
  5886. }
  5887. ItemDownloadTransaction.prototype = {
  5888.   _manager    : null,
  5889.   _downloads  : [],
  5890.   id          : -1,
  5891.   
  5892.   /**
  5893.    * Add a download to this transaction
  5894.    * @param   addon
  5895.    *          An object implementing nsIUpdateItem for the item to be downloaded
  5896.    * @param   id
  5897.    *          The integer identifier of this transaction
  5898.    */
  5899.   addDownload: function(addon, id) {
  5900.     this._downloads.push({ addon: addon, waiting: true });
  5901.     this._manager.datasource.addDownload(addon);
  5902.     this.id = id;
  5903.   },
  5904.   
  5905.   /**
  5906.    * Removes a download from this transaction
  5907.    * @param   url
  5908.    *          The URL to remove
  5909.    */
  5910.   removeDownload: function(url) {
  5911.     this._manager.datasource.removeDownload(url);
  5912.   },
  5913.   
  5914.   /**
  5915.    * Remove all downloads from this transaction
  5916.    */
  5917.   removeAllDownloads: function() {
  5918.     for (var i = 0; i < this._downloads.length; ++i) {
  5919.       var addon = this._downloads[i].addon;
  5920.       this.removeDownload(addon.xpiURL);
  5921.     }
  5922.   },
  5923.   
  5924.   /**
  5925.    * Determine if this transaction is handling the download of a url.
  5926.    * @param   url
  5927.    *          The URL to look for
  5928.    * @returns true if this transaction is downloading the supplied url.
  5929.    */
  5930.   containsURL: function(url) {
  5931.     for (var i = 0; i < this._downloads.length; ++i) {
  5932.       if (this._downloads[i].addon.xpiURL == url)
  5933.         return true;
  5934.     }
  5935.     return false;
  5936.   },
  5937.  
  5938.   /**
  5939.    * See nsIXPIProgressDialog.idl
  5940.    */
  5941.   onStateChange: function(index, state, value) {
  5942.     this._manager.onStateChange(this, this._downloads[index].addon, 
  5943.                                 state, value);
  5944.   },
  5945.   
  5946.   /**
  5947.    * See nsIXPIProgressDialog.idl
  5948.    */
  5949.   onProgress: function(index, value, maxValue) { 
  5950.     this._manager.onProgress(this._downloads[index].addon, value, maxValue);
  5951.   },
  5952.   
  5953.   /////////////////////////////////////////////////////////////////////////////
  5954.   // nsISupports
  5955.   QueryInterface: function(iid) {
  5956.     if (!iid.equals(Components.interfaces.nsIXPIProgressDialog) &&
  5957.         !iid.equals(Components.interfaces.nsISupports))
  5958.       throw Components.results.NS_ERROR_NO_INTERFACE;
  5959.     return this;
  5960.   }
  5961. };
  5962.  
  5963. /**
  5964.  * A listener object to the update check process that routes notifications to
  5965.  * the right places and keeps the datasource up to date.
  5966.  */
  5967. function AddonUpdateCheckListener(listener, datasource) {
  5968.   this._listener = listener;
  5969.   this._ds = datasource;
  5970. }
  5971. AddonUpdateCheckListener.prototype = {
  5972.   _listener: null,
  5973.   _ds: null,
  5974.   
  5975.   onUpdateStarted: function() {
  5976.     if (this._listener)
  5977.       this._listener.onUpdateStarted();
  5978.     this._ds.onUpdateStarted();
  5979.   },
  5980.   
  5981.   onUpdateEnded: function() {
  5982.     if (this._listener)
  5983.       this._listener.onUpdateEnded();
  5984.     this._ds.onUpdateEnded();
  5985.   },
  5986.   
  5987.   onAddonUpdateStarted: function(addon) {
  5988.     if (this._listener)
  5989.       this._listener.onAddonUpdateStarted(addon);
  5990.     this._ds.onAddonUpdateStarted(addon);
  5991.   },
  5992.   
  5993.   onAddonUpdateEnded: function(addon, status) {
  5994.     if (this._listener)
  5995.       this._listener.onAddonUpdateEnded(addon, status);
  5996.     this._ds.onAddonUpdateEnded(addon, status);
  5997.   }
  5998. };
  5999.  
  6000. ///////////////////////////////////////////////////////////////////////////////
  6001. //
  6002. // ExtensionItemUpdater
  6003. //
  6004. function ExtensionItemUpdater(aTargetAppID, aTargetAppVersion, aEM) 
  6005. {
  6006.   //this._appID = aTargetAppID;
  6007.   //this._appVersion = aTargetAppVersion;
  6008.   this._appID = firefoxAppID;
  6009.   this._appVersion = firefoxVersion;
  6010.   this._emDS = aEM._ds;
  6011.   this._em = aEM;
  6012.  
  6013.   getVersionChecker();
  6014. }
  6015.  
  6016. ExtensionItemUpdater.prototype = {
  6017.   _appID              : "",
  6018.   _appVersion         : "",
  6019.   _emDS               : null,
  6020.   _em                 : null,
  6021.   _versionUpdateOnly  : 0,
  6022.   _items              : [],
  6023.   _listener           : null,
  6024.   _background         : false,
  6025.   
  6026.   /////////////////////////////////////////////////////////////////////////////
  6027.   // ExtensionItemUpdater
  6028.   //
  6029.   // When we check for updates to an item, there are two pieces of information
  6030.   // that are returned - 1) info about the newest available version, if any,
  6031.   // and 2) info about the currently installed version. The latter is provided
  6032.   // primarily to inform the client of changes to the application compatibility 
  6033.   // metadata for the current item. Depending on the situation, either 2 or 
  6034.   // 1&2 may be what is required.
  6035.   //
  6036.   // Callers:
  6037.   //  1 - nsUpdateService.js, user event
  6038.   //      User clicked on the update icon to invoke an update check, 
  6039.   //      user clicked on an Extension/Theme and clicked "Update". In this
  6040.   //      case we want to update compatibility metadata about the installed
  6041.   //      version, and look for newer versions to offer. 
  6042.   //  2 - nsUpdateService.js, background event
  6043.   //      Timer fired, background update is being performed. In this case
  6044.   //      we also want to update compatibility metadata and look for newer
  6045.   //      versions.
  6046.   //  3 - Mismatch
  6047.   //      User upgraded to a newer version of the app, update compatibility
  6048.   //      metadata and look for newer versions.
  6049.   //  4 - Install Phone Home
  6050.   //      User installed an item that was deemed incompatible based only
  6051.   //      on the information provided in the item's install.rdf manifest, 
  6052.   //      we look ONLY for compatibility updates in this case to determine
  6053.   //      whether or not the item can be installed.
  6054.   //  
  6055.   checkForUpdates: function(aItems, aItemCount, aVersionUpdateOnly, 
  6056.                             aListener) {
  6057.     this._listener = new AddonUpdateCheckListener(aListener, this._emDS);
  6058.     if (this._listener)
  6059.       this._listener.onUpdateStarted();
  6060.     this._versionUpdateOnly = aVersionUpdateOnly;
  6061.     this._items = aItems;
  6062.     this._responseCount = aItemCount;
  6063.     
  6064.     // This is the number of extensions/themes/etc that we found updates for.
  6065.     this._updateCount = 0;
  6066.  
  6067.     for (var i = 0; i < aItemCount; ++i) {
  6068.       var e = this._items[i];
  6069.       if (this._listener)
  6070.         this._listener.onAddonUpdateStarted(e);
  6071.       (new RDFItemUpdater(this)).checkForUpdates(e, aVersionUpdateOnly);
  6072.     }
  6073.   },
  6074.   
  6075.   /////////////////////////////////////////////////////////////////////////////
  6076.   // ExtensionItemUpdater
  6077.   _applyVersionUpdates: function(aLocalItem, aRemoteItem) {
  6078.     var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS);
  6079.     // If targetAppInfo is null this is for a new install. If the local item's
  6080.     // maxVersion does not equal the targetAppInfo maxVersion then this is for
  6081.     // an upgrade. In both of these cases return true if the remotely specified
  6082.     // maxVersion is greater than the local item's maxVersion.
  6083.     if (!targetAppInfo ||
  6084.         gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) {
  6085.       if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0)
  6086.         return true;
  6087.       else
  6088.         return false;
  6089.     }
  6090.  
  6091.     if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) {
  6092.       // Remotely specified maxVersion is newer than the maxVersion 
  6093.       // for the installed Extension. Apply that change to the datasources.
  6094.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  6095.                                      aRemoteItem.maxAppVersion);
  6096.  
  6097.       // If we got here through |checkForMismatches|, this extension has
  6098.       // already been disabled, re-enable it.
  6099.       var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op;
  6100.       if (op == OP_NEEDS_DISABLE ||
  6101.           this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true")
  6102.         this._em._appEnableItem(aLocalItem.id);
  6103.       return true;
  6104.     }
  6105.     else if (this._versionUpdateOnly == 2)
  6106.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  6107.                                      aRemoteItem.maxAppVersion);
  6108.     return false;
  6109.   },
  6110.   
  6111.   _isValidUpdate: function(aLocalItem, aRemoteItem) {
  6112.     var appExtensionsVersion = firefoxVersion;
  6113.  
  6114.     // Check if the update will only run on a newer version of Firefox. 
  6115.     if (aRemoteItem.minAppVersion && 
  6116.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.minAppVersion) < 0) 
  6117.       return false;
  6118.  
  6119.     // Check if the update will only run on an older version of Firefox. 
  6120.     if (aRemoteItem.maxAppVersion && 
  6121.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.maxAppVersion) > 0) 
  6122.       return false;
  6123.  
  6124.     if (this._emDS.isBlocklisted(aRemoteItem.id, aRemoteItem.version,
  6125.                                  undefined, undefined))
  6126.       return false;
  6127.     
  6128.     return true;
  6129.   },
  6130.   
  6131.   checkForDone: function(item, status) {
  6132.     if (this._background &&
  6133.         status == nsIAddonUpdateCheckListener.STATUS_UPDATE) {
  6134.       var lastupdate = this._emDS.getItemProperty(item.id, "availableUpdateVersion");
  6135.       if (lastupdate != item.version)
  6136.         gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, true);
  6137.     }
  6138.     if (this._listener) {
  6139.       try {
  6140.         this._listener.onAddonUpdateEnded(item, status);
  6141.       }
  6142.       catch (e) {
  6143.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e);
  6144.       }
  6145.     }
  6146.     if (--this._responseCount == 0 && this._listener) {
  6147.       try {
  6148.         this._listener.onUpdateEnded();
  6149.       }
  6150.       catch (e) {
  6151.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
  6152.       }
  6153.     }
  6154.   },
  6155. };
  6156.  
  6157. function RDFItemUpdater(aUpdater) {
  6158.   this._updater = aUpdater;
  6159. }
  6160.  
  6161. RDFItemUpdater.prototype = {
  6162.   _updater            : null,
  6163.   _versionUpdateOnly  : 0,
  6164.   _item               : null,
  6165.   
  6166.   checkForUpdates: function(aItem, aVersionUpdateOnly) {
  6167.     // A preference setting can disable updating for this item
  6168.     try {
  6169.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) {
  6170.         var status = nsIAddonUpdateCheckListener.STATUS_DISABLED;
  6171.         this._updater.checkForDone(aItem, status);
  6172.         return;
  6173.       }
  6174.     }
  6175.     catch (e) { }
  6176.  
  6177.     // Items managed by the app are not checked for updates.
  6178.     var emDS = this._updater._emDS;
  6179.     if (emDS.getItemProperty(aItem.id, "appManaged") == "true") {
  6180.       var status = nsIAddonUpdateCheckListener.STATUS_APP_MANAGED;
  6181.       this._updater.checkForDone(aItem, status);
  6182.       return;
  6183.     }
  6184.  
  6185.     // Items that have a pending install, uninstall, or upgrade are not checked
  6186.     // for updates.
  6187.     var opType = emDS.getItemProperty(aItem.id, "opType");
  6188.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  6189.         opType == OP_NEEDS_UPGRADE) {
  6190.       var status = nsIAddonUpdateCheckListener.STATUS_PENDING_OP;
  6191.       this._updater.checkForDone(aItem, status);
  6192.       return;
  6193.     }
  6194.  
  6195.     var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id));
  6196.     // Don't check items for updates that are installed in a location that is
  6197.     // not managed by the app.
  6198.     if (installLocation && (installLocation.name == "winreg-app-global" ||
  6199.         installLocation.name == "winreg-app-user")) {
  6200.       var status = nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED;
  6201.       this._updater.checkForDone(aItem, status);
  6202.       return;
  6203.     }
  6204.  
  6205.     // Don't check items for updates if the location can't be written to except
  6206.     // when performing a version only update.
  6207.     if (!aVersionUpdateOnly && (!installLocation || !installLocation.canAccess)) {
  6208.       var status = nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
  6209.       this._updater.checkForDone(aItem, status);
  6210.       return;
  6211.     }
  6212.  
  6213.     this._versionUpdateOnly = aVersionUpdateOnly;
  6214.     this._item = aItem;
  6215.   
  6216.     var itemStatus;
  6217.     if (emDS.getItemProperty(aItem.id, "userDisabled") == "true" ||
  6218.         emDS.getItemProperty(aItem.id, "userDisabled") == OP_NEEDS_ENABLE)
  6219.       itemStatus = "userDisabled";
  6220.     else
  6221.       itemStatus = "userEnabled";
  6222.     
  6223.     if (emDS.getItemProperty(aItem.id, "compatible") == "false")
  6224.       itemStatus += ",incompatible";
  6225.     if (emDS.getItemProperty(aItem.id, "blocklisted") == "true")
  6226.       itemStatus += ",blocklisted";
  6227.     if (emDS.getItemProperty(aItem.id, "satisfiesDependencies") == "false")
  6228.       itemStatus += ",needsDependencies";
  6229.  
  6230.     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
  6231.     // install manifest, 3) the default configuration
  6232.     try {
  6233.       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
  6234.                                         Components.interfaces.nsIPrefLocalizedString).data;
  6235.     }
  6236.     catch (e) { }
  6237.     if (!dsURI)
  6238.       dsURI = aItem.updateRDF;
  6239.     if (!dsURI) {
  6240.       dsURI = gPref.getComplexValue(PREF_UPDATE_DEFAULT_URL,
  6241.                                     Components.interfaces.nsIPrefLocalizedString).data;
  6242.     }
  6243.     dsURI = dsURI.replace(/%ITEM_ID%/g, aItem.id);
  6244.     dsURI = dsURI.replace(/%ITEM_VERSION%/g, aItem.version);
  6245.     dsURI = dsURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
  6246.     dsURI = dsURI.replace(/%ITEM_STATUS%/g, itemStatus);
  6247.     dsURI = dsURI.replace(/%APP_ID%/g, this._updater._appID);
  6248.     dsURI = dsURI.replace(/%APP_VERSION%/g, this._updater._appVersion);
  6249.     dsURI = dsURI.replace(/%REQ_VERSION%/g, 1);
  6250.     dsURI = dsURI.replace(/%APP_OS%/g, gOSTarget);
  6251.     dsURI = dsURI.replace(/%APP_ABI%/g, gXPCOMABI);
  6252.     
  6253.     // escape() does not properly encode + symbols in any embedded FVF strings.
  6254.     dsURI = dsURI.replace(/\+/g, "%2B");
  6255.  
  6256.     // Verify that the URI provided is valid
  6257.     try {
  6258.       var uri = newURI(dsURI);
  6259.     }
  6260.     catch (e) {
  6261.       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" + 
  6262.           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
  6263.       this._updater.checkForDone(aItem, 
  6264.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6265.       return;
  6266.     }
  6267.  
  6268.     LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " + 
  6269.         uri.spec + ", item = " + aItem.objectSource);        
  6270.  
  6271.     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  6272.                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  6273.     request.open("GET", uri.spec, true);
  6274.     request.channel.notificationCallbacks = new BadCertHandler();
  6275.     request.overrideMimeType("text/xml");
  6276.     request.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
  6277.  
  6278.     var self = this;
  6279.     request.onerror     = function(event) { self.onXMLError(event, aItem);    };
  6280.     request.onload      = function(event) { self.onXMLLoad(event, aItem);     };
  6281.     request.send(null);
  6282.   },
  6283.  
  6284.   onXMLLoad: function(aEvent, aItem) {
  6285.     var request = aEvent.target;
  6286.     try {
  6287.       checkCert(request.channel);
  6288.     }
  6289.     catch (e) {
  6290.       // This may be overly restrictive in two cases: corporate installations
  6291.       // with a corporate update server using an in-house CA cert (installed
  6292.       // but not "built-in") and lone developers hosting their updates on a
  6293.       // site with a self-signed cert (permanently accepted, otherwise the
  6294.       // BadCertHandler would prevent getting this far). Update checks will
  6295.       // fail in both these scenarios.
  6296.       // How else can we protect the vast majority of updates served from AMO
  6297.       // from the spoofing attack described in bug 340198 while allowing those
  6298.       // other cases? A "hackme" pref? Domain-control certs are cheap, getting
  6299.       // one should not be a barrier in either case.
  6300.       LOG("RDFItemUpdater::onXMLLoad: " + e);
  6301.       this._updater.checkForDone(aItem,
  6302.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6303.       return;
  6304.     }
  6305.     var responseXML = request.responseXML;
  6306.  
  6307.     // If the item does not have an update RDF and returns an error it is not
  6308.     // treated as a failure since all items without an updateURL are checked
  6309.     // for updates on AMO even if they are not hosted there.
  6310.     if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
  6311.         (request.status != 200 && request.status != 0)) {
  6312.       this._updater.checkForDone(aItem, (aItem.updateRDF ? nsIAddonUpdateCheckListener.STATUS_FAILURE :
  6313.                                                            nsIAddonUpdateCheckListener.STATUS_NONE));
  6314.       return;
  6315.     }
  6316.  
  6317.     var rdfParser = Components.classes["@mozilla.org/rdf/xml-parser;1"]
  6318.                               .createInstance(Components.interfaces.nsIRDFXMLParser)
  6319.     var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
  6320.                        .createInstance(Components.interfaces.nsIRDFDataSource);
  6321.     rdfParser.parseString(ds, request.channel.URI, request.responseText);
  6322.  
  6323.     this.onDatasourceLoaded(ds, aItem);
  6324.   },
  6325.  
  6326.   onXMLError: function(aEvent, aItem) {
  6327.     try {
  6328.       var request = aEvent.target;
  6329.       // the following may throw (e.g. a local file or timeout)
  6330.       var status = request.status;
  6331.     }
  6332.     catch (e) {
  6333.       request = aEvent.target.channel.QueryInterface(Components.interfaces.nsIRequest);
  6334.       status = request.status;
  6335.     }
  6336.     // this can fail when a network connection is not present.
  6337.     try {
  6338.       var statusText = request.statusText;
  6339.     }
  6340.     catch (e) {
  6341.       status = 0;
  6342.     }
  6343.     // When status is 0 we don't have a valid channel.
  6344.     if (status == 0)
  6345.       statusText = "nsIXMLHttpRequest channel unavailable";
  6346.  
  6347.     LOG("RDFItemUpdater:onError: There was an error loading the \r\n" + 
  6348.         "the update datasource for item " + aItem.id + ", error: " + statusText);
  6349.     this._updater.checkForDone(aItem, 
  6350.                                nsIAddonUpdateCheckListener.STATUS_FAILURE);
  6351.   },
  6352.  
  6353.   onDatasourceLoaded: function(aDatasource, aLocalItem) {
  6354.     ///////////////////////////////////////////////////////////////////////////    
  6355.     // The extension update RDF file looks something like this:
  6356.     //
  6357.     //  <RDF:Description about="urn:mozilla:extension:{GUID}">
  6358.     //    <em:updates>
  6359.     //      <RDF:Seq>
  6360.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:4.9"/>
  6361.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:5.0"/>
  6362.     //      </RDF:Seq>
  6363.     //    </em:updates>
  6364.     //    <!-- the version of the extension being offered -->
  6365.     //    <em:version>5.0</em:version>
  6366.     //    <em:updateLink>http://www.mysite.com/myext-50.xpi</em:updateLink>
  6367.     //  </RDF:Description>
  6368.     //
  6369.     //  <RDF:Description about="urn:mozilla:extension:{GUID}:4.9">
  6370.     //    <em:version>4.9</em:version>
  6371.     //    <em:targetApplication>
  6372.     //      <RDF:Description>
  6373.     //        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
  6374.     //        <em:minVersion>0.9</em:minVersion>
  6375.     //        <em:maxVersion>1.0</em:maxVersion>
  6376.     //        <em:updateLink>http://www.mysite.com/myext-49.xpi</em:updateLink>
  6377.     //      </RDF:Description>
  6378.     //    </em:targetApplication>
  6379.     //  </RDF:Description>  
  6380.     //
  6381.     // If we get here because the following happened:
  6382.     // 1) User was using Firefox 0.9 with ExtensionX 0.5 (minVersion 0.8, 
  6383.     //    maxVersion 0.9 for Firefox)
  6384.     // 2) User upgraded Firefox to 1.0
  6385.     // 3) |checkForMismatches| deems ExtensionX 0.5 incompatible with this
  6386.     //    new version of Firefox on the basis of its maxVersion
  6387.     // 4) ** We reach this point **
  6388.     //
  6389.     // If the version of ExtensionX (0.5) matches that provided by the 
  6390.     // server, then this is a cue that the author updated the rdf file
  6391.     // or central repository to say "0.5 is ALSO compatible with Firefox 1.0,
  6392.     // no changes are necessary." In this event, the local metadata for
  6393.     // installed ExtensionX (0.5) is freshened with the new maxVersion, 
  6394.     // and we advance to the next item WITHOUT any download/install 
  6395.     // updates.
  6396.     if (!aDatasource.GetAllResources().hasMoreElements()) {
  6397.       LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" + 
  6398.           "If you are an Extension developer and were expecting there to be\r\n" + 
  6399.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  6400.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  6401.           "\r\nTry checking that: \r\n" + 
  6402.           " 1. Your remote RDF file exists at the location.\r\n" + 
  6403.           " 2. Your RDF file is valid XML (starts with <?xml version=\"1.0?\">\r\n" + 
  6404.           "    and loads in Firefox displaying pretty printed like other XML documents\r\n" + 
  6405.           " 3. Your server is sending the data in the correct MIME\r\n" + 
  6406.           "    type (text/xml)");
  6407.     }      
  6408.     
  6409.   
  6410.     // Parse the response RDF
  6411.     function UpdateData() {}; 
  6412.     UpdateData.prototype = { version: "0.0", updateLink: null, updateHash: null,
  6413.                              minVersion: "0.0", maxVersion: "0.0" };
  6414.     
  6415.     var versionUpdate = new UpdateData();
  6416.     var newestUpdate  = new UpdateData();
  6417.  
  6418.     var newerItem, sameItem;
  6419.     
  6420.     // Firefox 1.0PR+ update.rdf format
  6421.     if (!this._versionUpdateOnly) {
  6422.       // Look for newer versions of this item, we only do this in "normal" 
  6423.       // mode... see comment by ExtensionItemUpdater_checkForUpdates 
  6424.       // about how we do this in all cases but Install Phone Home - which 
  6425.       // only needs to do a version check.
  6426.       this._parseV20UpdateInfo(aDatasource, aLocalItem, newestUpdate, false);
  6427.  
  6428.       newerItem = makeItem(aLocalItem.id, 
  6429.                            newestUpdate.version, 
  6430.                            aLocalItem.installLocationKey,
  6431.                            newestUpdate.minVersion, 
  6432.                            newestUpdate.maxVersion, 
  6433.                            aLocalItem.name, 
  6434.                            newestUpdate.updateLink,
  6435.                            newestUpdate.updateHash,
  6436.                            "", /* Icon URL */
  6437.                            "", /* RDF Update URL */
  6438.                            aLocalItem.type);
  6439.       if (this._updater._isValidUpdate(aLocalItem, newerItem))
  6440.         ++this._updater._updateCount;
  6441.       else
  6442.         newerItem = null;
  6443.     }
  6444.     
  6445.     // Now look for updated version compatibility metadata for the currently
  6446.     // installed version...
  6447.     this._parseV20UpdateInfo(aDatasource, aLocalItem, versionUpdate, true);
  6448.  
  6449.     var result = gVersionChecker.compare(versionUpdate.version, 
  6450.                                           aLocalItem.version);
  6451.     if (result == 0) {
  6452.       // Local version exactly matches the "Version Update" remote version, 
  6453.       // Apply changes into local datasource.
  6454.       sameItem = makeItem(aLocalItem.id, 
  6455.                           versionUpdate.version, 
  6456.                           aLocalItem.installLocationKey,
  6457.                           versionUpdate.minVersion, 
  6458.                           versionUpdate.maxVersion, 
  6459.                           aLocalItem.name,
  6460.                           "", /* XPI Update URL */
  6461.                           "", /* XPI Update Hash */
  6462.                           "", /* Icon URL */
  6463.                           "", /* RDF Update URL */
  6464.                           aLocalItem.type);
  6465.       if (this._updater._isValidUpdate(aLocalItem, sameItem)) {
  6466.         // Install-time updates are not written to the DS because there is no
  6467.         // entry yet, EM just uses the notifications to ascertain (by hand)
  6468.         // whether or not there is a remote maxVersion tweak that makes the 
  6469.         // item being installed compatible.
  6470.         if (!this._updater._applyVersionUpdates(aLocalItem, sameItem))
  6471.           sameItem = null;
  6472.       }
  6473.       else 
  6474.         sameItem = null;
  6475.     }
  6476.     
  6477.     if (newerItem) {
  6478.       LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" + 
  6479.           newerItem.objectSource);
  6480.     }
  6481.     if (sameItem) {
  6482.       LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" + 
  6483.           "version of this item: " + sameItem.objectSource);
  6484.     }
  6485.     var item = null, status = nsIAddonUpdateCheckListener.STATUS_NONE;
  6486.     if (!this._versionUpdateOnly && newerItem) {
  6487.       item = newerItem;
  6488.       status = nsIAddonUpdateCheckListener.STATUS_UPDATE;
  6489.     }
  6490.     else if (sameItem) {
  6491.       item = sameItem;
  6492.       status = nsIAddonUpdateCheckListener.STATUS_VERSIONINFO;
  6493.     }
  6494.     else {
  6495.       item = aLocalItem;
  6496.       status = nsIAddonUpdateCheckListener.STATUS_NO_UPDATE;
  6497.     }
  6498.     // Only one call of this._updater.checkForDone is needed for RDF 
  6499.     // responses, since there is only one response per item.
  6500.     this._updater.checkForDone(item, status);
  6501.   },
  6502.  
  6503.   // Get a compulsory property from a resource. Reports an error if the 
  6504.   // property was not present. 
  6505.   _getPropertyFromResource: function(aDataSource, aSourceResource, aProperty, aLocalItem) {
  6506.     var rv;
  6507.     try {
  6508.       var property = gRDF.GetResource(EM_NS(aProperty));
  6509.       rv = stringData(aDataSource.GetTarget(aSourceResource, property, true));
  6510.       if (rv === undefined)
  6511.         throw Components.results.NS_ERROR_FAILURE;
  6512.     }
  6513.     catch (e) {
  6514.       // XXXben show console message "aProperty" not found on aSourceResource. 
  6515.       return null;
  6516.     }
  6517.     return rv;
  6518.   },
  6519.   
  6520.   // Parses Firefox 1.0RC1+ update.rdf format
  6521.   _parseV20UpdateInfo: function(aDataSource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  6522.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  6523.  
  6524.     var updatesArc = gRDF.GetResource(EM_NS("updates"));
  6525.     var updates = aDataSource.GetTarget(extensionRes, updatesArc, true);
  6526.     
  6527.     try {
  6528.       updates = updates.QueryInterface(Components.interfaces.nsIRDFResource);
  6529.     }
  6530.     catch (e) { 
  6531.       LOG("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" + 
  6532.           aLocalItem.id + "\r\n" + 
  6533.           "If you are an Extension developer and were expecting there to be\r\n" + 
  6534.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  6535.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  6536.           "\r\nTry checking that: \r\n" + 
  6537.           " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" + 
  6538.           "    RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" + 
  6539.           "    the <em:updates> listed all have matching GUIDs.");
  6540.       return; 
  6541.     }
  6542.     
  6543.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  6544.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  6545.     if (cu.IsContainer(aDataSource, updates)) {
  6546.       var ctr = getContainer(aDataSource, updates);
  6547.  
  6548.       // In "all update types" mode, we look for newer versions, starting with the 
  6549.       // current installed version.
  6550.       if (!aVersionUpdatesOnly) 
  6551.         aUpdateData.version = aLocalItem.version;
  6552.  
  6553.       var versions = ctr.GetElements();
  6554.       while (versions.hasMoreElements()) {
  6555.         // There are two different methodologies for collecting version 
  6556.         // information depending on whether or not we've bene invoked in 
  6557.         // "version updates only" mode or "version+newest" mode. 
  6558.         var version = versions.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6559.         this._parseV20Update(aDataSource, version, aLocalItem, aUpdateData, aVersionUpdatesOnly);
  6560.         if (aVersionUpdatesOnly && aUpdateData.updateLink)
  6561.           break;
  6562.       }
  6563.     }
  6564.   },
  6565.   
  6566.   _parseV20Update: function(aDataSource, aUpdateResource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  6567.     var version = this._getPropertyFromResource(aDataSource, aUpdateResource, 
  6568.                                                 "version", aLocalItem);
  6569.     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
  6570.     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
  6571.     while (targetApps.hasMoreElements()) {
  6572.       var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6573.       var id = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
  6574.       if (id != this._updater._appID)
  6575.         continue;
  6576.       
  6577.       var result = gVersionChecker.compare(version, aLocalItem.version);
  6578.       if (aVersionUpdatesOnly ? result == 0 : result > 0) {
  6579.         aUpdateData.version = version;
  6580.         aUpdateData.updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
  6581.         aUpdateData.updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
  6582.         aUpdateData.minVersion = this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem);
  6583.         aUpdateData.maxVersion = this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem);
  6584.       }
  6585.     }
  6586.   }
  6587. };
  6588.  
  6589. /**
  6590.  * A Datasource that holds Extensions. 
  6591.  * - Implements nsIRDFDataSource to drive UI
  6592.  * - Uses a RDF/XML datasource for storage (this is undesirable)
  6593.  * 
  6594.  * @constructor
  6595.  */
  6596. function ExtensionsDataSource(em) {
  6597.   this._em = em;
  6598.   
  6599.   this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT);
  6600.   this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME);
  6601.   gRDF.RegisterDataSource(this, true);
  6602. }
  6603. ExtensionsDataSource.prototype = {
  6604.   _inner    : null,
  6605.   _em       : null,
  6606.   _itemRoot     : null,
  6607.   _defaultTheme : null,
  6608.   
  6609.   /**
  6610.    * Determines if an item's dependencies are satisfied. An item's dependencies
  6611.    * are satisifed when all items specified in the item's em:requires arc are
  6612.    * installed, enabled, and the version is compatible based on the em:requires
  6613.    * minVersion and maxVersion.
  6614.    * @param   id
  6615.    *          The ID of the item
  6616.    * @returns true if the item's dependencies are satisfied.
  6617.    *          false if the item's dependencies are not satisfied.
  6618.    */
  6619.   satisfiesDependencies: function(id) {
  6620.     var ds = this._inner;
  6621.     var itemResource = getResourceForID(id);
  6622.     var targets = ds.GetTargets(itemResource, EM_R("requires"), true);
  6623.     if (!targets.hasMoreElements())
  6624.       return true;
  6625.  
  6626.     getVersionChecker();
  6627.     var idRes = EM_R("id");
  6628.     var minVersionRes = EM_R("minVersion");
  6629.     var maxVersionRes = EM_R("maxVersion");
  6630.     while (targets.hasMoreElements()) {
  6631.       var target = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6632.       var dependencyID = stringData(ds.GetTarget(target, idRes, true));
  6633.       var version = null;
  6634.       version = this.getItemProperty(dependencyID, "version");
  6635.       if (version) {
  6636.         var opType = this.getItemProperty(dependencyID, "opType");
  6637.         if (opType ==  OP_NEEDS_DISABLE || opType == OP_NEEDS_UNINSTALL)
  6638.           return false;
  6639.  
  6640.         if (this.getItemProperty(dependencyID, "userDisabled") == "true" ||
  6641.             this.getItemProperty(dependencyID, "appDisabled") == "true" ||
  6642.             this.getItemProperty(dependencyID, "userDisabled") == OP_NEEDS_DISABLE ||
  6643.             this.getItemProperty(dependencyID, "appDisabled") == OP_NEEDS_DISABLE)
  6644.           return false;
  6645.  
  6646.         var minVersion = stringData(ds.GetTarget(target, minVersionRes, true));
  6647.         var maxVersion = stringData(ds.GetTarget(target, maxVersionRes, true));
  6648.         var compatible = (gVersionChecker.compare(version, minVersion) >= 0 &&
  6649.                           gVersionChecker.compare(version, maxVersion) <= 0);
  6650.         if (!compatible)
  6651.           return false;
  6652.       }
  6653.       else {
  6654.         return false;
  6655.       }
  6656.     }
  6657.  
  6658.     return true;
  6659.   },
  6660.  
  6661.   /**
  6662.    * Determine if an item is compatible
  6663.    * @param   datasource
  6664.    *          The datasource to inspect for compatibility - can be the main
  6665.    *          datasource or an Install Manifest.
  6666.    * @param   source
  6667.    *          The RDF Resource of the item to inspect for compatibility.
  6668.    * @param   version
  6669.    *          The version of the application we are checking for compatibility
  6670.    *          against. If this parameter is undefined, the version of the running
  6671.    *          application is used.
  6672.    * @returns true if the item is compatible with this version of the 
  6673.    *          application, false, otherwise.
  6674.    */
  6675.   isCompatible: function (datasource, source, version) {
  6676.     // The Default Theme is always compatible. 
  6677.     if (source.EqualsNode(this._defaultTheme))
  6678.       return true;
  6679.  
  6680.     var foundFlock = false;
  6681.     var foundCompatibleFlock = false;
  6682.     var foundCompatibleFirefox = false;
  6683.  
  6684.     if (version === undefined) {
  6685.       version = gApp.version;
  6686.     }              
  6687.     var appID = gApp.ID;
  6688.     
  6689.     var extensionName = stringData(datasource.GetTarget(source, EM_R("name"), true));
  6690.  
  6691.     var targets = datasource.GetTargets(source, EM_R("targetApplication"), true);
  6692.     var idRes = EM_R("id");
  6693.     var minVersionRes = EM_R("minVersion");
  6694.     var maxVersionRes = EM_R("maxVersion");
  6695.     var versionChecker = getVersionChecker();
  6696.     while (targets.hasMoreElements()) {
  6697.       var targetApp = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6698.       var id          = stringData(datasource.GetTarget(targetApp, idRes, true));
  6699.       var minVersion  = stringData(datasource.GetTarget(targetApp, minVersionRes, true));
  6700.       var maxVersion  = stringData(datasource.GetTarget(targetApp, maxVersionRes, true));
  6701.  
  6702.       // Check our current appID first (Should be Flock).
  6703.       if (id == appID) {
  6704.         foundFlock = true;
  6705.         if (foundCompatibleFlock == false) {
  6706.           foundCompatibleFlock = ((versionChecker.compare(version, minVersion) >= 0) &&
  6707.                                   (versionChecker.compare(version, maxVersion) <= 0));
  6708.         }
  6709.       }
  6710.       else if (id == firefoxAppID && foundCompatibleFirefox == false) {
  6711.         foundCompatibleFirefox = ((versionChecker.compare(firefoxVersion, minVersion) >= 0) &&
  6712.                                   (versionChecker.compare(firefoxVersion, maxVersion) <= 0));
  6713.       }
  6714.     }
  6715.  
  6716.     if (foundCompatibleFlock) {
  6717.       return true;
  6718.     }
  6719.     else if (!foundFlock && foundCompatibleFirefox) {
  6720.       /*
  6721.        * This function is called for the following cases:
  6722.        * a) User selects to install extension by DND or clicking install button.
  6723.        * b) User installs extension with command line (--install-global-extension)
  6724.        * c) User upgraded Flock and now the extensions are checked with the new version.
  6725.        * d) Autoupdate has detected new version of extension and is upgrading.
  6726.        *
  6727.        * We only want to inform the user that there may be combatibility options for case a.
  6728.        * As far as I can tell the only difference is that the extensionManager is open only in case a.
  6729.        */
  6730.       try {
  6731.         // Put this in a try catch just incase there is no window mediator.
  6732.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  6733.                            .getService(Components.interfaces.nsIWindowMediator);
  6734.         var enumerator = wm.getEnumerator("Extension:Manager");
  6735.  
  6736.         if (!enumerator.hasMoreElements()) {
  6737.           // There are no extensionManager windows open so lets not bother the user.
  6738.           return true;
  6739.         }
  6740.       }
  6741.       catch (exceptionError) {
  6742.         LOG("nsExtensionmanager.js: isCompatible - Unable to get the WindowMediator.");
  6743.       }
  6744.       
  6745.       // NOTE TODO: We should actually ask if they want to install still.
  6746.       var extensionStrings = BundleManager.getBundle(URI_FLOCKKIT_PROPERTIES);
  6747.       var title = extensionStrings.GetStringFromName("notFullyCompatibleTitle");
  6748.       var params = [extensionName];
  6749.       var message = extensionStrings.formatStringFromName("notFullyCompatibleMsg", 
  6750.                                                            params, params.length);
  6751.       var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  6752.                          .getService(Components.interfaces.nsIPromptService);
  6753.       ps.alert(null, title, message);
  6754.       return true;
  6755.     }  
  6756.     
  6757.     return false;
  6758.   },
  6759.  
  6760.   /**
  6761.    * Determine if an item is blocklisted
  6762.    * @param   id
  6763.    *          The id of the item to check.
  6764.    * @param   extVersion
  6765.    *          The item's version.
  6766.    * @param   appVersion
  6767.    *          The version of the application we are checking in the blocklist.
  6768.    *          If this parameter is undefined, the version of the running
  6769.    *          application is used.
  6770.    * @param   toolkitVersion
  6771.    *          The version of the toolkit we are checking in the blocklist.
  6772.    *          If this parameter is undefined, the version of the running
  6773.    *          toolkit is used.
  6774.    * @returns true if the item is compatible with this version of the 
  6775.    *          application, false, otherwise.
  6776.    */
  6777.   isBlocklisted: function(id, extVersion, appVersion, toolkitVersion) {
  6778.     if (appVersion === undefined)
  6779.       appVersion = gApp.version;
  6780.     if (toolkitVersion === undefined)
  6781.       toolkitVersion = gApp.platformVersion;
  6782.  
  6783.     var blItem = Blocklist.entries[id];
  6784.     if (!blItem)
  6785.       return false;
  6786.  
  6787.     var versionChecker = getVersionChecker();
  6788.     for (var i = 0; i < blItem.length; ++i) {
  6789.       if (versionChecker.compare(extVersion, blItem[i].minVersion) < 0  ||
  6790.           versionChecker.compare(extVersion, blItem[i].maxVersion) > 0)
  6791.         continue;
  6792.  
  6793.       var blTargetApp = blItem[i].targetApps[gApp.ID];
  6794.       if (blTargetApp) {
  6795.         for (var x = 0; x < blTargetApp.length; ++x) {
  6796.           if (versionChecker.compare(appVersion, blTargetApp[x].minVersion) < 0  ||
  6797.               versionChecker.compare(appVersion, blTargetApp[x].maxVersion) > 0)
  6798.             continue;
  6799.           return true;
  6800.         }
  6801.       }
  6802.  
  6803.       blTargetApp = blItem[i].targetApps[TOOLKIT_ID];
  6804.       if (!blTargetApp)
  6805.         return false;
  6806.       for (x = 0; x < blTargetApp.length; ++x) {
  6807.         if (versionChecker.compare(toolkitVersion, blTargetApp[x].minVersion) < 0  ||
  6808.             versionChecker.compare(toolkitVersion, blTargetApp[x].maxVersion) > 0)
  6809.           continue;
  6810.         return true;
  6811.       }
  6812.     }
  6813.     return false;
  6814.   },
  6815.  
  6816.   /**
  6817.    * Gets a list of items that are incompatible with a specific application version.
  6818.    * @param   appID
  6819.    *          The ID of the application - XXXben unused?
  6820.    * @param   appVersion
  6821.    *          The Version of the application to check for incompatibility against.
  6822.    * @param   desiredType
  6823.    *          The nsIUpdateItem type of items to look for
  6824.    * @param   includeDisabled
  6825.    *          Whether or not disabled items should be included in the set returned
  6826.    * @returns An array of nsIUpdateItems that are incompatible with the application
  6827.    *          ID/Version supplied.
  6828.    */
  6829.   getIncompatibleItemList: function(appID, appVersion, desiredType, includeDisabled) {
  6830.     var items = [];
  6831.     var ctr = getContainer(this._inner, this._itemRoot);
  6832.     var elements = ctr.GetElements();
  6833.     while (elements.hasMoreElements()) {
  6834.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6835.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6836.       var type = this.getItemProperty(id, "type");
  6837.       // Skip this item if we're not seeking disabled items
  6838.       if (!includeDisabled && this.getItemProperty(id, "isDisabled") == "true")
  6839.         continue;
  6840.       
  6841.       // If the id of this item matches one of the items potentially installed
  6842.       // with and maintained by this application AND it is installed in the 
  6843.       // global install location (i.e. the place installed by the app installer)
  6844.       // it is and can be managed by the update file - it's not an item that has
  6845.       // been manually installed by the user into their profile dir, and as such
  6846.       // it is always compatible with the next release of the application since
  6847.       // we will continue to support it.
  6848.       var locationKey = this.getItemProperty(id, "installLocation");
  6849.       var appManaged = this.getItemProperty(id, "appManaged") == "true";
  6850.       if (appManaged && locationKey == KEY_APP_GLOBAL)
  6851.         continue;
  6852.  
  6853.       if (type != -1 && (type & desiredType) && 
  6854.           !this.isCompatible(this, item, appVersion))
  6855.         items.push(this.getItemForID(id));
  6856.     }
  6857.     return items;
  6858.   },
  6859.   
  6860.   /**
  6861.    * Retrieves a list of items that will be blocklisted by the application for
  6862.    * a specific application or toolkit version.
  6863.    * @param   appVersion
  6864.    *          The Version of the application to check the blocklist against.
  6865.    * @param   toolkitVersion
  6866.    *          The Version of the toolkit to check the blocklist against.
  6867.    * @param   desiredType
  6868.    *          The nsIUpdateItem type of items to look for
  6869.    * @param   includeAppDisabled
  6870.    *          Whether or not items that are or are already set to be disabled
  6871.    *          by the app on next restart should be included in the set returned
  6872.    * @returns An array of nsIUpdateItems that are blocklisted with the application
  6873.    *          or toolkit version supplied.
  6874.    */
  6875.   getBlocklistedItemList: function(appVersion, toolkitVersion, desiredType,
  6876.                                    includeAppDisabled) {
  6877.     var items = [];
  6878.     var ctr = getContainer(this._inner, this._itemRoot);
  6879.     var elements = ctr.GetElements();
  6880.     while (elements.hasMoreElements()) {
  6881.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6882.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6883.       var type = this.getItemProperty(id, "type");
  6884.  
  6885.       if (!includeAppDisabled &&
  6886.           (this.getItemProperty(id, "appDisabled") == "true" ||
  6887.           this.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE))
  6888.         continue;
  6889.  
  6890.       var extVersion = this.getItemProperty(id, "version");
  6891.       if (type != -1 && (type & desiredType) && 
  6892.           this.isBlocklisted(id, extVersion, appVersion, toolkitVersion))
  6893.         items.push(this.getItemForID(id));
  6894.     }
  6895.     return items;
  6896.   },
  6897.  
  6898.   /**
  6899.    * Gets a list of items of a specific type
  6900.    * @param   desiredType
  6901.    *          The nsIUpdateItem type of items to return
  6902.    * @param   countRef
  6903.    *          The XPCJS reference to the size of the returned array
  6904.    * @returns An array of nsIUpdateItems, populated only with an item for |id|
  6905.    *          if |id| is non-null, otherwise all items matching the specified
  6906.    *          type.
  6907.    */
  6908.   getItemList: function(desiredType, countRef) {
  6909.     var items = [];
  6910.     var ctr = getContainer(this, this._itemRoot);      
  6911.     var elements = ctr.GetElements();
  6912.     while (elements.hasMoreElements()) {
  6913.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6914.       var eID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  6915.       var type = this.getItemProperty(eID, "type");
  6916.       if (type != -1 && type & desiredType)
  6917.         items.push(this.getItemForID(eID));
  6918.     }
  6919.     countRef.value = items.length;
  6920.     return items;
  6921.   },
  6922.  
  6923.   /**
  6924.    * Retrieves a list of installed nsIUpdateItems of items that are dependent
  6925.    * on another item.
  6926.    * @param   id
  6927.    *          The ID of the item that other items depend on.
  6928.    * @param   includeDisabled
  6929.    *          Whether to include disabled items in the set returned.
  6930.    * @param   countRef
  6931.    *          The XPCJS reference to the number of items returned.
  6932.    * @returns An array of installed nsIUpdateItems that depend on the item
  6933.    *          specified by the id parameter.
  6934.    */
  6935.   getDependentItemListForID: function(id, includeDisabled, countRef) {
  6936.     var items = [];
  6937.     var ds = this._inner;
  6938.     var ctr = getContainer(this, this._itemRoot);
  6939.     var elements = ctr.GetElements();
  6940.     while (elements.hasMoreElements()) {
  6941.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6942.       var dependentID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  6943.       var targets = ds.GetTargets(e, EM_R("requires"), true);
  6944.       var idRes = EM_R("id");
  6945.       while (targets.hasMoreElements()) {
  6946.         var target = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6947.         var dependencyID = stringData(ds.GetTarget(target, idRes, true));
  6948.         if (dependencyID == id) {
  6949.           if (!includeDisabled && this.getItemProperty(dependentID, "isDisabled") == "true")
  6950.             continue;
  6951.           items.push(this.getItemForID(dependentID));
  6952.           break;
  6953.         }
  6954.       }
  6955.     }
  6956.     countRef.value = items.length;
  6957.     return items;
  6958.   },
  6959.  
  6960.   /**
  6961.    * Get a list of Item IDs that have a flag set
  6962.    * @param   flag
  6963.    *          The name of an RDF property (less EM_NS) to check for
  6964.    * @param   desiredType
  6965.    *          The nsIUpdateItem type of item to look for
  6966.    * @returns An array of Item IDs 
  6967.    *
  6968.    * XXXben - this function is a little weird since it returns an array of 
  6969.    *          strings, not an array of nsIUpdateItems...  
  6970.    */
  6971.   getItemsWithFlagUnset: function(flag, desiredType) {
  6972.     var items = [];
  6973.  
  6974.     var ctr = getContainer(this, this._itemRoot);    
  6975.     var elements = ctr.GetElements();
  6976.     while (elements.hasMoreElements()) {
  6977.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6978.       var id = stripPrefix(e.Value, PREFIX_ITEM_URI);
  6979.       var type = this.getItemProperty(id, "type");
  6980.       if (type != -1 && type & desiredType) {
  6981.         var value = this.GetTarget(e, EM_R(flag), true);
  6982.         if (!value)
  6983.           items.push(id);
  6984.       }
  6985.     }
  6986.     return items;
  6987.   },
  6988.   
  6989.   /**
  6990.    * Constructs an nsIUpdateItem for the given item ID
  6991.    * @param   id
  6992.    *          The GUID of the item to construct a nsIUpdateItem for
  6993.    * @returns The nsIUpdateItem for the id.
  6994.    */  
  6995.   getItemForID: function(id) {
  6996.     var r = getResourceForID(id);
  6997.     if (!r)
  6998.       return null;
  6999.     
  7000.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  7001.     var updateHash = this.getItemProperty(id, "availableUpdateHash");
  7002.     return makeItem(id, 
  7003.                     this.getItemProperty(id, "version"), 
  7004.                     this.getItemProperty(id, "installLocation"),
  7005.                     targetAppInfo ? targetAppInfo.minVersion : "",
  7006.                     targetAppInfo ? targetAppInfo.maxVersion : "",
  7007.                     this.getItemProperty(id, "name"),
  7008.                     this.getItemProperty(id, "availableUpdateURL"),
  7009.                     updateHash ? updateHash : "",
  7010.                     this.getItemProperty(id, "iconURL"), 
  7011.                     this.getItemProperty(id, "updateURL"), 
  7012.                     this.getItemProperty(id, "type"));
  7013.   },
  7014.   
  7015.   /**
  7016.    * Gets the name of the Install Location where an item is installed.
  7017.    * @param   id
  7018.    *          The GUID of the item to locate an Install Location for
  7019.    * @returns The string name of the Install Location where the item is 
  7020.    *          installed.
  7021.    */
  7022.   getInstallLocationKey: function(id) {
  7023.     return this.getItemProperty(id, "installLocation");
  7024.   },
  7025.   
  7026.   /**
  7027.    * Sets an RDF property on an item in a datasource. Does not create
  7028.    * multiple assertions
  7029.    * @param   datasource
  7030.    *          The target datasource where the property should be set
  7031.    * @param   source
  7032.    *          The RDF Resource to set the property on
  7033.    * @param   property
  7034.    *          The RDF Resource of the property to set
  7035.    * @param   newValue
  7036.    *          The RDF Node containing the new property value
  7037.    */
  7038.   _setProperty: function(datasource, source, property, newValue) {
  7039.     var oldValue = datasource.GetTarget(source, property, true);
  7040.     if (oldValue) {
  7041.       if (newValue)
  7042.         datasource.Change(source, property, oldValue, newValue);
  7043.       else
  7044.         datasource.Unassert(source, property, oldValue);
  7045.     }
  7046.     else if (newValue)
  7047.       datasource.Assert(source, property, newValue, true);
  7048.   },
  7049.   
  7050.   /**
  7051.    * Sets the target application info for an item in the Extensions
  7052.    * datasource and in the item's install manifest if it is installed in a
  7053.    * profile's extensions directory, it exists, and we have write access.
  7054.    * @param   id
  7055.    *          The ID of the item to update target application info for
  7056.    * @param   minVersion
  7057.    *          The minimum version of the target application that this item can
  7058.    *          run in
  7059.    * @param   maxVersion
  7060.    *          The maximum version of the target application that this item can
  7061.    *          run in
  7062.    */
  7063.   updateTargetAppInfo: function(id, minVersion, maxVersion)
  7064.   {
  7065.     // Update the Extensions datasource
  7066.     this.setTargetApplicationInfo(id, minVersion, maxVersion, null);
  7067.  
  7068.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  7069.     if (installLocation.name != KEY_APP_PROFILE)
  7070.       return;
  7071.  
  7072.     var installManifestFile = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  7073.     // Only update if the item exists and we can write to the location
  7074.     if (installManifestFile.exists() && installLocation.canAccess)
  7075.       this.setTargetApplicationInfo(id, minVersion, maxVersion,
  7076.                                     getInstallManifest(installManifestFile));
  7077.   },
  7078.  
  7079.   /**
  7080.    * Gets the updated target application info if it exists for an item from
  7081.    * the Extensions datasource during an installation or upgrade.
  7082.    * @param   id
  7083.    *          The ID of the item to discover updated target application info for
  7084.    * @returns A JS Object with the following properties:
  7085.    *          "id"            The id of the item
  7086.    *          "minVersion"    The updated minimum version of the target
  7087.    *                          application that this item can run in
  7088.    *          "maxVersion"    The updated maximum version of the target
  7089.    *                          application that this item can run in
  7090.    */
  7091.   getUpdatedTargetAppInfo: function(id) {
  7092.     // The default theme is always compatible so there is never update info.
  7093.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  7094.       return null;
  7095.  
  7096.     var appID = gApp.ID;
  7097.     var r = getResourceForID(id);
  7098.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  7099.     if (!targetApps.hasMoreElements())
  7100.       targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  7101.     while (targetApps.hasMoreElements()) {
  7102.       var targetApp = targetApps.getNext();
  7103.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  7104.         try {
  7105.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  7106.           if (foundAppID != appID) // Different target application
  7107.             continue;
  7108.           var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true);
  7109.           var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true);
  7110.           if (updatedMinVersion && updatedMaxVersion)
  7111.             return { id        : id,
  7112.                      minVersion: stringData(updatedMinVersion),
  7113.                      maxVersion: stringData(updatedMaxVersion) };
  7114.           else
  7115.             return null;
  7116.         }
  7117.         catch (e) { 
  7118.           continue;
  7119.         }
  7120.       }
  7121.     }
  7122.     return null;
  7123.   },
  7124.   
  7125.   /**
  7126.    * Sets the updated target application info for an item in the Extensions
  7127.    * datasource during an installation or upgrade.
  7128.    * @param   id
  7129.    *          The ID of the item to set updated target application info for
  7130.    * @param   updatedMinVersion
  7131.    *          The updated minimum version of the target application that this
  7132.    *          item can run in
  7133.    * @param   updatedMaxVersion
  7134.    *          The updated maximum version of the target application that this
  7135.    *          item can run in
  7136.    */
  7137.   setUpdatedTargetAppInfo: function(id, updatedMinVersion, updatedMaxVersion) {
  7138.     // The default theme is always compatible so it is never updated.
  7139.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  7140.       return;
  7141.  
  7142.     // Version/Dependency Info
  7143.     var updatedMinVersionRes = EM_R("updatedMinVersion");
  7144.     var updatedMaxVersionRes = EM_R("updatedMaxVersion");
  7145.  
  7146.     var appID = gApp.ID;
  7147.     var r = getResourceForID(id);
  7148.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  7149.     // add updatedMinVersion and updatedMaxVersion for an install else an upgrade
  7150.     if (!targetApps.hasMoreElements()) {
  7151.       var idRes = EM_R("id");
  7152.       var targetRes = getResourceForID(id);
  7153.       var property = EM_R("targetApplication");
  7154.       var anon = gRDF.GetAnonymousResource();
  7155.       this._inner.Assert(anon, idRes, EM_L(appID), true);
  7156.       this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  7157.       this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  7158.       this._inner.Assert(targetRes, property, anon, true);
  7159.     }
  7160.     else {
  7161.       while (targetApps.hasMoreElements()) {
  7162.         var targetApp = targetApps.getNext();
  7163.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  7164.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  7165.           if (foundAppID != appID) // Different target application
  7166.             continue;
  7167.           this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  7168.           this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  7169.           break;
  7170.         }
  7171.       }
  7172.     }
  7173.     this.Flush();
  7174.   },
  7175.  
  7176.   /**
  7177.    * Gets the target application info for an item from a datasource.
  7178.    * @param   id
  7179.    *          The GUID of the item to discover target application info for
  7180.    * @param   datasource
  7181.    *          The datasource to look up target application info in
  7182.    * @returns A JS Object with the following properties:
  7183.    *          "minVersion"    The minimum version of the target application
  7184.    *                          that this item can run in
  7185.    *          "maxVersion"    The maximum version of the target application
  7186.    *                          that this item can run in
  7187.    *          or null, if no target application data exists for the specified
  7188.    *          id in the supplied datasource.
  7189.    */
  7190.   getTargetApplicationInfo: function(id, datasource) {
  7191.     // The default theme is always compatible. 
  7192.     if (getResourceForID(id).EqualsNode(this._defaultTheme)) {
  7193.       var ver = gApp.version;
  7194.       return { minVersion: ver, maxVersion: ver };
  7195.     }
  7196.     var firefoxAppInfo = null;
  7197.     var appID = gApp.ID;
  7198.     var r = getResourceForID(id);
  7199.     var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true);
  7200.     if (!targetApps)
  7201.       return null;
  7202.     if (!targetApps.hasMoreElements())
  7203.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  7204.     while (targetApps.hasMoreElements()) {
  7205.       var targetApp = targetApps.getNext();
  7206.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  7207.         try {
  7208.           var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true));
  7209.  
  7210.           var appInfo = {
  7211.             appID: foundAppID,
  7212.             minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)),
  7213.             maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true))
  7214.           };
  7215.  
  7216.           if (foundAppID == appID)
  7217.             return appInfo;
  7218.           else if (foundAppID == firefoxAppID)
  7219.             firefoxAppInfo = appInfo;
  7220.           else
  7221.             continue;
  7222.         }
  7223.         catch (e) { 
  7224.           continue;
  7225.         }
  7226.       }
  7227.     }
  7228.     if (firefoxAppInfo)
  7229.       return firefoxAppInfo;
  7230.     return null;
  7231.   },
  7232.   
  7233.   /**
  7234.    * Sets the target application info for an item in a datasource.
  7235.    * @param   id
  7236.    *          The GUID of the item to discover target application info for
  7237.    * @param   minVersion
  7238.    *          The minimum version of the target application that this item can
  7239.    *          run in
  7240.    * @param   maxVersion
  7241.    *          The maximum version of the target application that this item can
  7242.    *          run in
  7243.    * @param   datasource
  7244.    *          The datasource to loko up target application info in
  7245.    */
  7246.   setTargetApplicationInfo: function(id, minVersion, maxVersion, datasource) {
  7247.     var targetDataSource = datasource;
  7248.     if (!targetDataSource)
  7249.       targetDataSource = this._inner;
  7250.       
  7251.     var appID = gApp.ID;
  7252.     var r = getResourceForID(id);
  7253.     var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true);
  7254.     if (!targetApps.hasMoreElements())
  7255.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  7256.     while (targetApps.hasMoreElements()) {
  7257.       var targetApp = targetApps.getNext();
  7258.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  7259.         var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true));
  7260.         // Different target application
  7261.         if (foundAppID != appID && foundAppID != firefoxAppID)
  7262.           continue;
  7263.         
  7264.         this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion));
  7265.         this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion));
  7266.         
  7267.         // If we were setting these properties on the main datasource, flush
  7268.         // it now. (Don't flush changes set on Install Manifests - they are
  7269.         // fleeting).
  7270.         if (!datasource)
  7271.           this.Flush();
  7272.  
  7273.         break;
  7274.       }
  7275.     }
  7276.   },
  7277.   
  7278.   /** 
  7279.    * Gets a property of an item
  7280.    * @param   id
  7281.    *          The GUID of the item
  7282.    * @param   property
  7283.    *          The name of the property (excluding EM_NS)
  7284.    * @returns The literal value of the property, or undefined if there is no 
  7285.    *          value.
  7286.    */
  7287.   getItemProperty: function(id, property) { 
  7288.     var item = getResourceForID(id);
  7289.     if (!item) {
  7290.       LOG("getItemProperty failing for lack of an item. This means getResourceForItem \
  7291.            failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")");
  7292.     }
  7293.     else 
  7294.       return this._getItemProperty(item, property);
  7295.     return undefined;
  7296.   },
  7297.   
  7298.   /**
  7299.    * Gets a property of an item resource
  7300.    * @param   itemResource
  7301.    *          The RDF Resource of the item
  7302.    * @param   property
  7303.    *          The name of the property (excluding EM_NS)
  7304.    * @returns The literal value of the property, or undefined if there is no
  7305.    *          value.
  7306.    */
  7307.   _getItemProperty: function(itemResource, property) {
  7308.     var target = this.GetTarget(itemResource, EM_R(property), true);
  7309.     var value = stringData(target);
  7310.     if (value === undefined)
  7311.       value = intData(target);
  7312.     return value === undefined ? "" : value;
  7313.   },
  7314.   
  7315.   /**
  7316.    * Sets a property on an item.
  7317.    * @param   id
  7318.    *          The GUID of the item
  7319.    * @param   propertyArc
  7320.    *          The RDF Resource of the property arc
  7321.    * @param   propertyValue
  7322.    *          A nsIRDFLiteral value of the property to be set
  7323.    */
  7324.   setItemProperty: function (id, propertyArc, propertyValue) {
  7325.     var item = getResourceForID(id);
  7326.     this._setProperty(this._inner, item, propertyArc, propertyValue);
  7327.     this.Flush();  
  7328.   },
  7329.  
  7330.   /**
  7331.    * Inserts the RDF resource for an item into a container.
  7332.    * @param   id
  7333.    *          The GUID of the item
  7334.    */
  7335.   insertItemIntoContainer: function(id) {
  7336.     // Get the target container and resource
  7337.     var ctr = getContainer(this._inner, this._itemRoot);
  7338.     var itemResource = getResourceForID(id);
  7339.     // Don't bother adding the extension to the list if it's already there. 
  7340.     // (i.e. we're upgrading)
  7341.     var oldIndex = ctr.IndexOf(itemResource);
  7342.     if (oldIndex == -1)
  7343.       ctr.AppendElement(itemResource);
  7344.     this.Flush();
  7345.   }, 
  7346.  
  7347.   /**
  7348.    * Removes the RDF resource for an item from its container.
  7349.    * @param   id
  7350.    *          The GUID of the item
  7351.    */
  7352.   removeItemFromContainer: function(id) {
  7353.     var ctr = getContainer(this._inner, this._itemRoot);
  7354.     var itemResource = getResourceForID(id);
  7355.     ctr.RemoveElement(itemResource, true);
  7356.     this.Flush();
  7357.   },
  7358.  
  7359.   /**
  7360.    * Removes a corrupt item entry from the extension list added due to buggy 
  7361.    * code in previous EM versions!  
  7362.    * @param   id
  7363.    *          The GUID of the item
  7364.    */
  7365.   removeCorruptItem: function(id) {
  7366.     this.removeItemMetadata(id);
  7367.     this.removeItemFromContainer(id);
  7368.   },
  7369.  
  7370.   /**
  7371.    * Removes a corrupt download entry from the list
  7372.    * @param   uri
  7373.    *          The RDF URI of the item.
  7374.    * @returns The RDF Resource of the removed entry 
  7375.    */
  7376.   removeCorruptDLItem: function(uri) {
  7377.     var itemResource = gRDF.GetResource(uri);
  7378.     var ctr = getContainer(this._inner, this._itemRoot);
  7379.     if (ctr.IndexOf(itemResource) != -1) {
  7380.       ctr.RemoveElement(itemResource, true);
  7381.       this._cleanResource(itemResource);
  7382.       this.Flush();
  7383.     }
  7384.     return itemResource;
  7385.   },
  7386.   
  7387.   /**
  7388.    * Copies metadata from an Install Manifest Datasource into the Extensions
  7389.    * DataSource.
  7390.    * @param   id
  7391.    *          The GUID of the item
  7392.    * @param   installManifest
  7393.    *          The Install Manifest datasource we are copying from
  7394.    * @param   installLocation
  7395.    *          The Install Location of the item. 
  7396.    */
  7397.   addItemMetadata: function(id, installManifest, installLocation) {
  7398.     // Copy the assertions over from the source datasource. 
  7399.     var targetRes = getResourceForID(id);
  7400.     // Assert properties with single values
  7401.     var singleProps = ["version", "name", "description", "creator", "homepageURL", 
  7402.                        "updateURL", "updateService", "optionsURL", "aboutURL", 
  7403.                        "iconURL", "internalName"];
  7404.  
  7405.     // Items installed into restricted Install Locations can also be locked 
  7406.     // (can't be removed or disabled), and hidden (not shown in the UI)
  7407.     if (installLocation.restricted)
  7408.       singleProps = singleProps.concat(["locked", "hidden"]);
  7409.     if (installLocation.name == KEY_APP_GLOBAL) 
  7410.       singleProps = singleProps.concat(["appManaged"]);
  7411.     for (var i = 0; i < singleProps.length; ++i) {
  7412.       var property = EM_R(singleProps[i]);
  7413.       var literal = installManifest.GetTarget(gInstallManifestRoot, property, true);
  7414.       // If literal is null, _setProperty will remove any existing.
  7415.       this._setProperty(this._inner, targetRes, property, literal);
  7416.     }    
  7417.     
  7418.     // Assert properties with multiple values    
  7419.     var manyProps = ["developer", "translator", "contributor"];
  7420.     for (var i = 0; i < manyProps.length; ++i) {
  7421.       var property = EM_R(manyProps[i]);
  7422.       var literals = installManifest.GetTargets(gInstallManifestRoot, property, true);
  7423.       
  7424.       var oldValues = this._inner.GetTargets(targetRes, property, true);
  7425.       while (oldValues.hasMoreElements()) {
  7426.         var oldValue = oldValues.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  7427.         this._inner.Unassert(targetRes, property, oldValue);
  7428.       }
  7429.       while (literals.hasMoreElements()) {
  7430.         var literal = literals.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  7431.         this._inner.Assert(targetRes, property, literal, true);
  7432.       }
  7433.     }
  7434.  
  7435.     // Version/Dependency Info
  7436.     var versionProps = ["targetApplication", "requires"];
  7437.     var idRes = EM_R("id");
  7438.     var minVersionRes = EM_R("minVersion");
  7439.     var maxVersionRes = EM_R("maxVersion");
  7440.     for (var i = 0; i < versionProps.length; ++i) {
  7441.       var property = EM_R(versionProps[i]);
  7442.       var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true);
  7443.  
  7444.       var oldVersionInfos = this._inner.GetTargets(targetRes, property, true);
  7445.       while (oldVersionInfos.hasMoreElements()) {
  7446.         var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7447.         this._cleanResource(oldVersionInfo);
  7448.         this._inner.Unassert(targetRes, property, oldVersionInfo);
  7449.       }
  7450.       while (newVersionInfos.hasMoreElements()) {
  7451.         var newVersionInfo = newVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7452.         var anon = gRDF.GetAnonymousResource();
  7453.         this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true);
  7454.         this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true);
  7455.         this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true);
  7456.         this._inner.Assert(targetRes, property, anon, true);
  7457.       }
  7458.     }
  7459.     this.updateProperty(id, "opType");
  7460.     this.updateProperty(id, "updateable");
  7461.     this.Flush();
  7462.   },
  7463.   
  7464.   /**
  7465.    * Strips an item entry of all assertions.
  7466.    * @param   id
  7467.    *          The GUID of the item
  7468.    */
  7469.   removeItemMetadata: function(id) {
  7470.     var item = getResourceForID(id);
  7471.     var resources = ["targetApplication", "requires"];
  7472.     for (var i = 0; i < resources.length; ++i) {
  7473.       var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true);
  7474.       while (targetApps.hasMoreElements()) {
  7475.         var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7476.         this._cleanResource(targetApp);
  7477.       }
  7478.     }
  7479.  
  7480.     this._cleanResource(item);
  7481.   },
  7482.   
  7483.   /**
  7484.    * Strips a resource of all outbound assertions. We use methods like this 
  7485.    * since the RDFXMLDatasource will write out all assertions, even if they
  7486.    * are not connected through our root. 
  7487.    * @param   resource
  7488.    *          The resource to clean. 
  7489.    */
  7490.   _cleanResource: function(resource) {
  7491.     // Remove outward arcs
  7492.     var arcs = this._inner.ArcLabelsOut(resource);
  7493.     while (arcs.hasMoreElements()) {
  7494.       var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7495.       var targets = this._inner.GetTargets(resource, arc, true);
  7496.       while (targets.hasMoreElements()) {
  7497.         var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  7498.         if (value)
  7499.           this._inner.Unassert(resource, arc, value);
  7500.       }
  7501.     }
  7502.   },
  7503.   
  7504.   /**
  7505.    * Notify views that this propery has changed (this is for properties that
  7506.    * are implemented by this datasource rather than by the inner in-memory
  7507.    * datasource and thus do not get free change handling).
  7508.    * @param   id 
  7509.    *          The GUID of the item to update the property for.
  7510.    * @param   property
  7511.    *          The property (less EM_NS) to update.
  7512.    */
  7513.   updateProperty: function(id, property) {
  7514.     var item = getResourceForID(id);
  7515.     this._updateProperty(item, property);
  7516.   },
  7517.   
  7518.   /**
  7519.    * Notify views that this propery has changed (this is for properties that
  7520.    * are implemented by this datasource rather than by the inner in-memory
  7521.    * datasource and thus do not get free change handling). This allows updating
  7522.    * properties for download items which don't have the em item prefix in there
  7523.    ( resource value. In most instances updateProperty should be used.
  7524.    * @param   item
  7525.    *          The item to update the property for.
  7526.    * @param   property
  7527.    *          The property (less EM_NS) to update.
  7528.    */
  7529.   _updateProperty: function(item, property) {
  7530.     var propertyResource = EM_R(property);
  7531.     var value = this.GetTarget(item, propertyResource, true);
  7532.     if (item && value) {
  7533.       for (var i = 0; i < this._observers.length; ++i)
  7534.         this._observers[i].onChange(this, item, propertyResource, 
  7535.                                     EM_L(""), value);
  7536.     }
  7537.   },
  7538.   
  7539.   /**
  7540.    * Move an Item to the index of another item in its container.
  7541.    * @param   movingID
  7542.    *          The ID of the item to be moved.
  7543.    * @param   destinationID
  7544.    *          The ID of an item to move another item to.
  7545.    */
  7546.   moveToIndexOf: function(movingID, destinationID) {
  7547.     var extensions = gRDF.GetResource(RDFURI_ITEM_ROOT);
  7548.     var ctr = getContainer(this._inner, extensions);
  7549.     var item = gRDF.GetResource(movingID);
  7550.     var index = ctr.IndexOf(gRDF.GetResource(destinationID));
  7551.     if (index == -1)
  7552.       index = 1; // move to the beginning if destinationID is not found
  7553.     this._inner.beginUpdateBatch();
  7554.     ctr.RemoveElement(item, true);
  7555.     ctr.InsertElementAt(item, index, true);
  7556.     this._inner.endUpdateBatch();
  7557.     this.Flush();
  7558.   },
  7559.  
  7560.   /**
  7561.    * Sorts addons of the specified type by the specified property starting from
  7562.    * the top of their container. If the addons are already sorted then no action
  7563.    * is performed.
  7564.    * @param   type
  7565.    *          The nsIUpdateItem type of the items to sort.
  7566.    * @param   propertyName
  7567.    *          The RDF property name used for sorting.
  7568.    * @param   isAscending
  7569.    *          true to sort ascending and false to sort descending
  7570.    */
  7571.   sortTypeByProperty: function(type, propertyName, isAscending) {
  7572.     var items = [];
  7573.     var ctr = getContainer(this._inner, this._itemRoot);
  7574.     var elements = ctr.GetElements();
  7575.     // Base 0 ordinal for checking against the existing order after sorting
  7576.     var ordinal = 0;
  7577.     while (elements.hasMoreElements()) {
  7578.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7579.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7580.       var itemType = this.getItemProperty(id, "type");
  7581.       if (itemType & type) {
  7582.         items.push({ item   : item,
  7583.                      ordinal: ordinal,
  7584.                      sortkey: this.getItemProperty(id, propertyName).toLowerCase() });
  7585.         ordinal++;
  7586.       }
  7587.     }
  7588.  
  7589.     var direction = isAscending ? 1 : -1; 
  7590.     // Case insensitive sort
  7591.     function compare(a, b) {
  7592.         if (a.sortkey < b.sortkey) return (-1 * direction);
  7593.         if (a.sortkey > b.sortkey) return (1 * direction);
  7594.         return 0;
  7595.     }
  7596.     items.sort(compare);
  7597.  
  7598.     // Check if there are any changes in the order of the items
  7599.     var isDirty = false;
  7600.     for (var i = 0; i < items.length; i++) {
  7601.       if (items[i].ordinal != i) {
  7602.         isDirty = true;
  7603.         break;
  7604.       }
  7605.     }
  7606.  
  7607.     // If there are no changes then early return to avoid the perf impact
  7608.     if (!isDirty)
  7609.       return;
  7610.  
  7611.     // Reorder the items by moving them to the top of the container
  7612.     this.beginUpdateBatch();
  7613.     for (i = 0; i < items.length; i++) {
  7614.       ctr.RemoveElement(items[i].item, true);
  7615.       ctr.InsertElementAt(items[i].item, i + 1, true);
  7616.     }
  7617.     this.endUpdateBatch();
  7618.     this.Flush();
  7619.   },
  7620.  
  7621.   /**
  7622.    * Determines if an Item is an active download
  7623.    * @param   id
  7624.    *          The ID of the item. This will be a uri scheme without the
  7625.    *          em item prefix so getProperty shouldn't be used.
  7626.    * @returns true if the item is an active download, false otherwise.
  7627.    */
  7628.   isDownloadItem: function(id) {
  7629.     var downloadURL = stringData(this.GetTarget(gRDF.GetResource(id), EM_R("downloadURL"), true));
  7630.     return downloadURL && downloadURL != "";
  7631.   },
  7632.  
  7633.   /**
  7634.    * Adds an entry representing an active download to the appropriate container
  7635.    * @param   addon
  7636.    *          An object implementing nsIUpdateItem for the addon being 
  7637.    *          downloaded.
  7638.    */
  7639.   addDownload: function(addon) {
  7640.     // Updates have already been added to the datasource so we just update the
  7641.     // download state.
  7642.     if (addon.id != addon.xpiURL) {
  7643.       this.updateDownloadState(PREFIX_ITEM_URI + addon.id, "waiting");
  7644.       return;
  7645.     }
  7646.     var res = gRDF.GetResource(addon.xpiURL);
  7647.     this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name));
  7648.     this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version));
  7649.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL));
  7650.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL));
  7651.     this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type));
  7652.  
  7653.     var ctr = getContainer(this._inner, this._itemRoot);
  7654.     if (ctr.IndexOf(res) == -1)
  7655.       ctr.AppendElement(res);
  7656.     
  7657.     this.updateDownloadState(addon.xpiURL, "waiting");
  7658.     this.Flush();
  7659.   },
  7660.   
  7661.   /**
  7662.    * Adds an entry representing an item that is incompatible and is being
  7663.    * checked for a compatibility update.
  7664.    * @param   name
  7665.    *          The display name of the item being checked
  7666.    * @param   url
  7667.    *          The URL string of the xpi file that has been staged.
  7668.    * @param   type
  7669.    *          The nsIUpdateItem type of the item
  7670.    * @param   version
  7671.    *          The version of the item
  7672.    */
  7673.   addIncompatibleUpdateItem: function(name, url, type, version) {
  7674.     var iconURL = (type == nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME :
  7675.                                                        URI_GENERIC_ICON_XPINSTALL;
  7676.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  7677.     var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage",
  7678.                                                            [BundleManager.appName, name], 2)
  7679.  
  7680.     var res = gRDF.GetResource(url);
  7681.     this._setProperty(this._inner, res, EM_R("name"), EM_L(name));
  7682.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL));
  7683.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url));
  7684.     this._setProperty(this._inner, res, EM_R("type"), EM_I(type));
  7685.     this._setProperty(this._inner, res, EM_R("version"), EM_L(version));
  7686.     this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true"));
  7687.     this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg));
  7688.  
  7689.     var ctr = getContainer(this._inner, this._itemRoot);
  7690.     if (ctr.IndexOf(res) == -1)
  7691.       ctr.AppendElement(res);
  7692.  
  7693.     this.updateDownloadState(url, "incompatibleUpdate");
  7694.     this.Flush();
  7695.   },
  7696.  
  7697.   /**
  7698.    * Removes an active download from the appropriate container
  7699.    * @param   url
  7700.    *          The URL string of the active download to be removed
  7701.    */
  7702.   removeDownload: function(url) {
  7703.     var res = gRDF.GetResource(url);
  7704.     var ctr = getContainer(this._inner, this._itemRoot);
  7705.     if (ctr.IndexOf(res) != -1) 
  7706.       ctr.RemoveElement(res, true);
  7707.     this._cleanResource(res);
  7708.     this.updateDownloadState(url, null);
  7709.     this.Flush();
  7710.   },
  7711.   
  7712.   /**
  7713.    * A hash of RDF resource values (e.g. Add-on IDs or XPI URLs) that represent
  7714.    * installation progress for a single browser session.
  7715.    */
  7716.   _progressData: { },
  7717.  
  7718.   /**
  7719.    * Updates the install progress data for a given ID (e.g. Add-on IDs or
  7720.    * XPI URLs).
  7721.    * @param   id
  7722.    *          The URL string of the active download to be removed
  7723.    * @param   state
  7724.    *          The current state in the installation process. If null the object
  7725.    *          is deleted from _progressData.
  7726.    */
  7727.   updateDownloadState: function(id, state) {
  7728.     if (!state) {
  7729.       if (id in this._progressData)
  7730.         delete this._progressData[id];
  7731.       return;
  7732.     }
  7733.     else {
  7734.       if (!(id in this._progressData)) 
  7735.         this._progressData[id] = { };
  7736.       this._progressData[id].state = state;
  7737.     }
  7738.     var item = gRDF.GetResource(id);
  7739.     this._updateProperty(item, "state");
  7740.   },
  7741.  
  7742.   updateDownloadProgress: function(id, progress) {
  7743.     if (!progress) {
  7744.       if (!(id in this._progressData))
  7745.         return;
  7746.       this._progressData[id].progress = null;
  7747.     }
  7748.     else {
  7749.       if (!(id in this._progressData))
  7750.         this.updateDownloadState(id, "downloading");
  7751.  
  7752.       if (this._progressData[id].progress == progress)
  7753.         return;
  7754.  
  7755.       this._progressData[id].progress = progress;
  7756.     }
  7757.     var item = gRDF.GetResource(id);
  7758.     this._updateProperty(item, "progress");
  7759.   },
  7760.  
  7761.   /**
  7762.    * A GUID->location-key hash of items that are visible to the application.
  7763.    * These are items that show up in the Extension/Themes etc UI. If there is
  7764.    * an instance of the same item installed in Install Locations of differing 
  7765.    * profiles, the item at the highest priority location will appear in this 
  7766.    * list.
  7767.    */
  7768.   visibleItems: { },
  7769.   
  7770.   /**
  7771.    * Walk the list of installed items and determine what the visible list is, 
  7772.    * based on which items are visible at the highest priority locations. 
  7773.    */  
  7774.   _buildVisibleItemList: function() {
  7775.     var ctr = getContainer(this, this._itemRoot);
  7776.     var items = ctr.GetElements();
  7777.     while (items.hasMoreElements()) {
  7778.       var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  7779.       // Resource URIs adopt the format: location-key,item-id
  7780.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7781.       this.visibleItems[id] = this.getItemProperty(id, "installLocation");
  7782.     }
  7783.   },
  7784.   
  7785.   /**
  7786.    * Updates an item's location in the visible item list.
  7787.    * @param   id
  7788.    *          The GUID of the item to update
  7789.    * @param   locationKey
  7790.    *          The name of the Install Location where the item is installed.
  7791.    * @param   forceReplace
  7792.    *          true if the new location should be used, regardless of its 
  7793.    *          priority relationship to existing entries, false if the location
  7794.    *          should only be updated if its priority is lower than the existing
  7795.    *          value.
  7796.    */
  7797.   updateVisibleList: function(id, locationKey, forceReplace) {
  7798.     if (id in this.visibleItems && this.visibleItems[id]) {
  7799.       var oldLocation = InstallLocations.get(this.visibleItems[id]);
  7800.       var newLocation = InstallLocations.get(locationKey);
  7801.       if (forceReplace || newLocation.priority < oldLocation.priority) 
  7802.         this.visibleItems[id] = locationKey;
  7803.     }
  7804.     else 
  7805.       this.visibleItems[id] = locationKey;
  7806.   },
  7807.  
  7808.   /**
  7809.    * Load the Extensions Datasource from disk.
  7810.    */
  7811.   loadExtensions: function() {
  7812.     Blocklist._ensureBlocklist();
  7813.     var extensionsFile  = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  7814.     try {
  7815.       this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile));
  7816.     }
  7817.     catch (e) {
  7818.       LOG("Datasource::loadExtensions: removing corrupted extensions datasource " +
  7819.           " file = " + extensionsFile.path + ", exception = " + e + "\n");
  7820.       extensionsFile.remove(false);
  7821.       return;
  7822.     }
  7823.  
  7824.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  7825.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  7826.     cu.MakeSeq(this._inner, this._itemRoot);
  7827.  
  7828.     this._buildVisibleItemList();
  7829.   },
  7830.   
  7831.   /**
  7832.    * See nsIExtensionManager.idl
  7833.    */
  7834.   onUpdateStarted: function() {
  7835.     LOG("Datasource: Update Started");
  7836.   },
  7837.   
  7838.   /**
  7839.    * See nsIExtensionManager.idl
  7840.    */
  7841.   onUpdateEnded: function() {
  7842.     LOG("Datasource: Update Ended");
  7843.   },
  7844.   
  7845.   /**
  7846.    * See nsIExtensionManager.idl
  7847.    */
  7848.   onAddonUpdateStarted: function(addon) {
  7849.     LOG("Datasource: Addon Update Started: " + addon.id);
  7850.     this.updateProperty(addon.id, "availableUpdateURL");
  7851.   },
  7852.   
  7853.   /**
  7854.    * See nsIExtensionManager.idl
  7855.    */
  7856.   onAddonUpdateEnded: function(addon, status) {
  7857.     LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status);
  7858.     var url = null, hash = null, version = null;
  7859.     var updateAvailable = status == nsIAddonUpdateCheckListener.STATUS_UPDATE;
  7860.     if (updateAvailable) {
  7861.       url = EM_L(addon.xpiURL);
  7862.       if (addon.xpiHash)
  7863.         hash = EM_L(addon.xpiHash);
  7864.       version = EM_L(addon.version);
  7865.     }
  7866.     this.setItemProperty(addon.id, EM_R("availableUpdateURL"), url);
  7867.     this.setItemProperty(addon.id, EM_R("availableUpdateHash"), hash);
  7868.     this.setItemProperty(addon.id, EM_R("availableUpdateVersion"), version);
  7869.     this.updateProperty(addon.id, "availableUpdateURL");
  7870.   },
  7871.  
  7872.   /////////////////////////////////////////////////////////////////////////////
  7873.   // nsIRDFDataSource
  7874.   get URI() {
  7875.     return "rdf:extensions";
  7876.   },
  7877.   
  7878.   GetSource: function(property, target, truthValue) {
  7879.     return this._inner.GetSource(property, target, truthValue);
  7880.   },
  7881.   
  7882.   GetSources: function(property, target, truthValue) {
  7883.     return this._inner.GetSources(property, target, truthValue);
  7884.   },
  7885.   
  7886.   /**
  7887.    * Gets an URL to a theme's image file
  7888.    * @param   item
  7889.    *          The RDF Resource representing the item 
  7890.    * @param   fileName
  7891.    *          The file to locate a URL for
  7892.    * @param   fallbackURL
  7893.    *          If the location fails, supply this URL instead
  7894.    * @returns An RDF Resource to the URL discovered, or the fallback
  7895.    *          if the discovery failed. 
  7896.    */
  7897.   _getThemeImageURL: function(item, fileName, fallbackURL) {
  7898.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7899.     var installLocation = this._em.getInstallLocation(id);
  7900.     var file = installLocation.getItemFile(id, fileName)
  7901.     if (file.exists())
  7902.       return gRDF.GetResource(getURLSpecFromFile(file));
  7903.  
  7904.     if (id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)) {
  7905.       var jarFile = getFile(KEY_APPDIR, [DIR_CHROME, FILE_DEFAULT_THEME_JAR]);
  7906.       var url = "jar:" + getURLSpecFromFile(jarFile) + "!/" + fileName;
  7907.       return gRDF.GetResource(url);
  7908.     }
  7909.  
  7910.     return fallbackURL ? gRDF.GetResource(fallbackURL) : null;
  7911.   },
  7912.  
  7913.   /**
  7914.    * Get the em:iconURL property (icon url of the item)
  7915.    */
  7916.   _rdfGet_iconURL: function(item, property) {
  7917.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7918.     var type = this.getItemProperty(id, "type");
  7919.     if (type & nsIUpdateItem.TYPE_THEME)
  7920.       return this._getThemeImageURL(item, "icon.png", URI_GENERIC_ICON_THEME);
  7921.  
  7922.     if (inSafeMode())
  7923.       return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  7924.  
  7925.     var hasIconURL = this._inner.hasArcOut(item, property);
  7926.     // If the addon doesn't have an IconURL property or it is disabled use the
  7927.     // generic icon URL instead.
  7928.     if (!hasIconURL || this.getItemProperty(id, "isDisabled") == "true")
  7929.       return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  7930.     var iconURL = stringData(this._inner.GetTarget(item, property, true));
  7931.     try {
  7932.       var uri = newURI(iconURL);
  7933.       var scheme = uri.scheme;
  7934.       // Only allow chrome URIs or when installing http(s) URIs.
  7935.       if (scheme == "chrome" || (scheme == "http" || scheme == "https") &&
  7936.           this._inner.hasArcOut(item, EM_R("downloadURL")))
  7937.         return null;
  7938.     }
  7939.     catch (e) {
  7940.     }
  7941.     // Use a generic icon URL for addons that have an invalid iconURL.
  7942.     return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  7943.   },
  7944.   
  7945.   /**
  7946.    * Get the em:previewImage property (preview image of the item)
  7947.    */
  7948.   _rdfGet_previewImage: function(item, property) {
  7949.     var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type");
  7950.     if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  7951.       return this._getThemeImageURL(item, "preview.png", null);
  7952.     return null;
  7953.   },
  7954.   
  7955.   /**
  7956.    * If we're in safe mode, the item is disabled by the user or app, or the
  7957.    * item is to be upgraded force the generic about dialog for the item.
  7958.    */
  7959.   _rdfGet_aboutURL: function(item, property) {
  7960.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7961.     if (inSafeMode() || this.getItemProperty(id, "isDisabled") == "true" ||
  7962.         this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE)
  7963.       return EM_L("");
  7964.  
  7965.     return null;
  7966.   },
  7967.  
  7968.   _rdfGet_installDate: function(item, property) {
  7969.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7970.     var key = this.getItemProperty(id, "installLocation");
  7971.     if (key && key in StartupCache.entries && id in StartupCache.entries[key] &&
  7972.         StartupCache.entries[key][id] && StartupCache.entries[key][id].mtime)
  7973.       return EM_D(StartupCache.entries[key][id].mtime * 1000000);
  7974.     return null;
  7975.   },
  7976.  
  7977.   /**
  7978.    * Get the em:compatible property (whether or not this item is compatible)
  7979.    */
  7980.   _rdfGet_compatible: function(item, property) {
  7981.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  7982.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  7983.     if (!targetAppInfo) {
  7984.       // When installing a new addon targetAppInfo does not exist yet
  7985.       if (this.getItemProperty(id, "opType") == OP_NEEDS_INSTALL)
  7986.         return EM_L("true");
  7987.       return EM_L("false");
  7988.     }
  7989.     
  7990.     getVersionChecker();
  7991.     var appVersion = gApp.version;
  7992.  
  7993.     if (targetAppInfo.appID == gApp.ID) {
  7994.       // Check Flock
  7995.       if ((gVersionChecker.compare(targetAppInfo.maxVersion, appVersion) < 0) ||
  7996.           (gVersionChecker.compare(appVersion, targetAppInfo.minVersion) < 0)) {
  7997.         return EM_L("false");
  7998.       }
  7999.     }
  8000.     else if (targetAppInfo.id == firefoxAppID) {
  8001.       if ((gVersionChecker.compare(targetAppInfo.maxVersion, firefoxVersion) < 0) ||
  8002.           (gVersionChecker.compare(firefoxVersion, targetAppInfo.minVersion) < 0)) {
  8003.         return EM_L("false");
  8004.       }
  8005.     }
  8006.     return EM_L("true");
  8007.   }, 
  8008.  
  8009.   /**
  8010.    * Get the em:blocklisted property (whether or not this item is blocklisted)
  8011.    */
  8012.   _rdfGet_blocklisted: function(item, property) {
  8013.     Blocklist._ensureBlocklist();
  8014.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8015.     var blItem = Blocklist.entries[id];
  8016.     if (!blItem)
  8017.       return EM_L("false");
  8018.  
  8019.     getVersionChecker();
  8020.     var version = this.getItemProperty(id, "version");
  8021.     var appVersion = gApp.version;
  8022.     for (var i = 0; i < blItem.length; ++i) {
  8023.       if (gVersionChecker.compare(version, blItem[i].minVersion) < 0  ||
  8024.           gVersionChecker.compare(version, blItem[i].maxVersion) > 0)
  8025.         continue;
  8026.  
  8027.       var blTargetApp = blItem[i].targetApps[gApp.ID];
  8028.       if (blTargetApp) {
  8029.         for (var x = 0; x < blTargetApp.length; ++x) {
  8030.           if (gVersionChecker.compare(appVersion, blTargetApp[x].minVersion) < 0  ||
  8031.               gVersionChecker.compare(appVersion, blTargetApp[x].maxVersion) > 0)
  8032.             continue;
  8033.           return EM_L("true");
  8034.         }
  8035.       }
  8036.  
  8037.       blTargetApp = blItem[i].targetApps[TOOLKIT_ID];
  8038.       if (!blTargetApp)
  8039.         return EM_L("false");
  8040.       for (x = 0; x < blTargetApp.length; ++x) {
  8041.         if (gVersionChecker.compare(gApp.platformVersion, blTargetApp[x].minVersion) < 0  ||
  8042.             gVersionChecker.compare(gApp.platformVersion, blTargetApp[x].maxVersion) > 0)
  8043.           continue;
  8044.         return EM_L("true");
  8045.       }
  8046.     }
  8047.     return EM_L("false");
  8048.   }, 
  8049.   
  8050.   /**
  8051.    * Get the em:state property (represents the current phase of an install).
  8052.    */
  8053.   _rdfGet_state: function(item, property) {
  8054.     var id = item.Value;
  8055.     if (id in this._progressData)
  8056.       return EM_L(this._progressData[id].state);
  8057.     return null;
  8058.   },
  8059.  
  8060.   /**
  8061.    * Get the em:progress property from the _progressData js object. By storing
  8062.    * progress which is updated repeastedly during a download we avoid
  8063.    * repeastedly writing it to the rdf file.
  8064.    */
  8065.   _rdfGet_progress: function(item, property) {
  8066.     var id = item.Value;
  8067.     if (id in this._progressData)
  8068.       return EM_I(this._progressData[id].progress);
  8069.     return null;
  8070.   },
  8071.  
  8072.   /**
  8073.    * Get the em:appManaged property. This prevents extensions from hiding
  8074.    * extensions installed into locations other than the app-global location.
  8075.    */
  8076.   _rdfGet_appManaged: function(item, property) {
  8077.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8078.     var locationKey = this.getItemProperty(id, "installLocation");
  8079.     if (locationKey != KEY_APP_GLOBAL)
  8080.       return EM_L("false");
  8081.     return null;
  8082.   },
  8083.  
  8084.   /**
  8085.    * Get the em:hidden property. This prevents extensions from hiding
  8086.    * extensions installed into locations other than restricted locations.
  8087.    */
  8088.   _rdfGet_hidden: function(item, property) {
  8089.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8090.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8091.     if (!installLocation || !installLocation.restricted)
  8092.       return EM_L("false");
  8093.     return null;
  8094.   },
  8095.  
  8096.   /**
  8097.    * Get the em:locked property. This prevents extensions from locking
  8098.    * extensions installed into locations other than restricted locations.
  8099.    */
  8100.   _rdfGet_locked: function(item, property) {
  8101.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8102.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8103.     if (!installLocation || !installLocation.restricted)
  8104.       return EM_L("false");
  8105.     return null;
  8106.   },
  8107.  
  8108.   /** 
  8109.    * Gets the em:availableUpdateURL - the URL to an XPI update package, if
  8110.    * present, or a literal string "none" if there is no update XPI URL.
  8111.    * XXXrstrong we return none due to bug 331689 
  8112.    */
  8113.   _rdfGet_availableUpdateURL: function(item, property) {
  8114.     var value = this._inner.GetTarget(item, property, true);
  8115.     if (!value) 
  8116.       return EM_L("none");
  8117.     return value;
  8118.   },
  8119.  
  8120.   /**
  8121.    * Get the em:satisfiesDependencies property - literal string "false" for
  8122.    * dependencies not satisfied (e.g. dependency disabled, incorrect version,
  8123.    * not installed etc.), and literal string "true" for dependencies satisfied.
  8124.    */
  8125.   _rdfGet_satisfiesDependencies: function(item, property) {
  8126.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8127.     if (this.satisfiesDependencies(id))
  8128.       return EM_L("true");
  8129.     return EM_L("false");
  8130.   },
  8131.   
  8132.   /**
  8133.    * Get the em:opType property (controls widget state for the EM UI)
  8134.    * from the Startup Cache (e.g. extensions.cache)
  8135.    * XXXrstrong we return none for OP_NONE due to bug 331689 
  8136.    */
  8137.   _rdfGet_opType: function(item, property) {
  8138.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8139.     var key = this.getItemProperty(id, "installLocation");
  8140.     if (key in StartupCache.entries && id in StartupCache.entries[key] &&
  8141.         StartupCache.entries[key][id] && StartupCache.entries[key][id].op != OP_NONE)
  8142.       return EM_L(StartupCache.entries[key][id].op);
  8143.     return EM_L("none");
  8144.   },
  8145.  
  8146.   /**
  8147.    * Gets a localizable property. Install Manifests are generally only in one 
  8148.    * language, however an item can customize by providing localized prefs in 
  8149.    * the form:
  8150.    *
  8151.    *    extensions.{GUID}.[name|description|creator|homepageURL]
  8152.    *
  8153.    * to specify localized text for each of these properties.
  8154.    */
  8155.   _getLocalizablePropertyValue: function(item, property) {
  8156.     // These are localizable properties that a language pack supplied by the 
  8157.     // Extension may override.          
  8158.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  8159.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  8160.                     stripPrefix(property.Value, PREFIX_NS_EM);
  8161.     try {
  8162.       var value = gPref.getComplexValue(prefName, 
  8163.                                         Components.interfaces.nsIPrefLocalizedString);
  8164.       if (value.data) 
  8165.         return EM_L(value.data);
  8166.     }
  8167.     catch (e) {
  8168.     }
  8169.     return null;
  8170.   },
  8171.   
  8172.   /**
  8173.    * Get the em:name property (name of the item)
  8174.    */
  8175.   _rdfGet_name: function(item, property) {
  8176.     return this._getLocalizablePropertyValue(item, property);
  8177.   },
  8178.   
  8179.   /**
  8180.    * Get the em:description property (description of the item)
  8181.    */
  8182.   _rdfGet_description: function(item, property) {
  8183.     return this._getLocalizablePropertyValue(item, property);
  8184.   },
  8185.   
  8186.   /**
  8187.    * Get the em:creator property (creator of the item)
  8188.    */
  8189.   _rdfGet_creator: function(item, property) { 
  8190.     return this._getLocalizablePropertyValue(item, property);
  8191.   },
  8192.   
  8193.   /**
  8194.    * Get the em:homepageURL property (homepage URL of the item)
  8195.    */
  8196.   _rdfGet_homepageURL: function(item, property) {
  8197.     return this._getLocalizablePropertyValue(item, property);
  8198.   },
  8199.  
  8200.   /**
  8201.    * Get the em:isDisabled property. This will be true if the item has a
  8202.    * appDisabled or a userDisabled property that is true or OP_NEEDS_ENABLE.
  8203.    */
  8204.   _rdfGet_isDisabled: function(item, property) {
  8205.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8206.     if (this.getItemProperty(id, "userDisabled") == "true" ||
  8207.         this.getItemProperty(id, "appDisabled") == "true" ||
  8208.         this.getItemProperty(id, "userDisabled") == OP_NEEDS_ENABLE ||
  8209.         this.getItemProperty(id, "appDisabled") == OP_NEEDS_ENABLE)
  8210.       return EM_L("true");
  8211.     return EM_L("false");
  8212.   },
  8213.  
  8214.   _rdfGet_addonID: function(item, property) {
  8215.     var id = this._inner.GetTarget(item, EM_R("downloadURL"), true) ? item.Value :
  8216.                                                                       stripPrefix(item.Value, PREFIX_ITEM_URI);
  8217.     return EM_L(id);
  8218.   },
  8219.  
  8220.   /**
  8221.    * Get the em:updateable property - this specifies whether the item is
  8222.    * allowed to be updated
  8223.    */
  8224.   _rdfGet_updateable: function(item, property) {
  8225.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  8226.     var opType = this.getItemProperty(id, "opType");
  8227.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  8228.         opType == OP_NEEDS_UPGRADE ||
  8229.         this.getItemProperty(id, "appManaged") == "true")
  8230.       return EM_L("false");
  8231.  
  8232.     if (getPref("getBoolPref", (PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id), false)) == true)
  8233.       return EM_L("false");
  8234.  
  8235.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  8236.     if (!installLocation || !installLocation.canAccess)
  8237.       return EM_L("false");
  8238.  
  8239.     return EM_L("true");
  8240.   },
  8241.  
  8242.   /**
  8243.    * See nsIRDFDataSource.idl
  8244.    */
  8245.   GetTarget: function(source, property, truthValue) {
  8246.     if (!source)
  8247.       return null;
  8248.       
  8249.     var target = null;
  8250.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  8251.     if (getter in this)
  8252.       target = this[getter](source, property);
  8253.  
  8254.     return target || this._inner.GetTarget(source, property, truthValue);
  8255.   },
  8256.   
  8257.   /**
  8258.    * Gets an enumeration of values of a localizable property. Install Manifests
  8259.    * are generally only in one language, however an item can customize by 
  8260.    * providing localized prefs in the form:
  8261.    *
  8262.    *    extensions.{GUID}.[contributor].1
  8263.    *    extensions.{GUID}.[contributor].2
  8264.    *    extensions.{GUID}.[contributor].3
  8265.    *    ...
  8266.    *
  8267.    * to specify localized text for each of these properties.
  8268.    */
  8269.   _getLocalizablePropertyValues: function(item, property) {
  8270.     // These are localizable properties that a language pack supplied by the 
  8271.     // Extension may override.          
  8272.     var values = [];
  8273.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  8274.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  8275.                     stripPrefix(property.Value, PREFIX_NS_EM);
  8276.     var i = 0;
  8277.     while (true) {
  8278.       try {
  8279.         var value = gPref.getComplexValue(prefName + "." + ++i, 
  8280.                                           Components.interfaces.nsIPrefLocalizedString);
  8281.         if (value.data) 
  8282.           values.push(EM_L(value.data));
  8283.       }
  8284.       catch (e) {
  8285.         try {
  8286.           var value = gPref.getComplexValue(prefName, 
  8287.                                             Components.interfaces.nsIPrefLocalizedString);
  8288.           if (value.data) 
  8289.             values.push(EM_L(value.data));
  8290.         }
  8291.         catch (e) {
  8292.         }
  8293.         break;
  8294.       }
  8295.     }
  8296.     return values.length > 0 ? values : null;
  8297.   },
  8298.  
  8299.   /**
  8300.    * Get the em:developer property (developers of the extension)
  8301.    */
  8302.   _rdfGets_developer: function(item, property) {
  8303.     return this._getLocalizablePropertyValues(item, property); 
  8304.   },
  8305.  
  8306.   /**
  8307.    * Get the em:translator property (translators of the extension)
  8308.    */
  8309.   _rdfGets_translator: function(item, property) {
  8310.     return this._getLocalizablePropertyValues(item, property); 
  8311.   },
  8312.   
  8313.   /**
  8314.    * Get the em:contributor property (contributors to the extension)
  8315.    */
  8316.   _rdfGets_contributor: function(item, property) {
  8317.     return this._getLocalizablePropertyValues(item, property); 
  8318.   },
  8319.   
  8320.   /**
  8321.    * See nsIRDFDataSource.idl
  8322.    */
  8323.   GetTargets: function(source, property, truthValue) {
  8324.     if (!source)
  8325.       return null;
  8326.       
  8327.     var ary = null;
  8328.     var propertyName = stripPrefix(property.Value, PREFIX_NS_EM);
  8329.     var getter = "_rdfGets_" + propertyName;
  8330.     if (getter in this)
  8331.       ary = this[getter](source, property);
  8332.     else {
  8333.       // The template builder calls GetTargets when single value properties
  8334.       // are used in a triple.
  8335.       getter = "_rdfGet_" + propertyName;
  8336.       if (getter in this)
  8337.         ary = [ this[getter](source, property) ];
  8338.     }
  8339.     
  8340.     return ary ? new ArrayEnumerator(ary) 
  8341.                : this._inner.GetTargets(source, property, truthValue);
  8342.   },
  8343.   
  8344.   Assert: function(source, property, target, truthValue) {
  8345.     this._inner.Assert(source, property, target, truthValue);
  8346.   },
  8347.   
  8348.   Unassert: function(source, property, target) {
  8349.     this._inner.Unassert(source, property, target);
  8350.   },
  8351.   
  8352.   Change: function(source, property, oldTarget, newTarget) {
  8353.     this._inner.Change(source, property, oldTarget, newTarget);
  8354.   },
  8355.  
  8356.   Move: function(oldSource, newSource, property, target) {
  8357.     this._inner.Move(oldSource, newSource, property, target);
  8358.   },
  8359.   
  8360.   HasAssertion: function(source, property, target, truthValue) {
  8361.     if (!source || !property || !target)
  8362.       return false;
  8363.  
  8364.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  8365.     if (getter in this)
  8366.       return this[getter](source, property) == target;
  8367.     return this._inner.HasAssertion(source, property, target, truthValue);
  8368.   },
  8369.   
  8370.   _observers: [],
  8371.   AddObserver: function(observer) {
  8372.     for (var i = 0; i < this._observers.length; ++i) {
  8373.       if (this._observers[i] == observer) 
  8374.         return;
  8375.     }
  8376.     this._observers.push(observer);
  8377.     this._inner.AddObserver(observer);
  8378.   },
  8379.   
  8380.   RemoveObserver: function(observer) {
  8381.     for (var i = 0; i < this._observers.length; ++i) {
  8382.       if (this._observers[i] == observer) 
  8383.         this._observers.splice(i, 1);
  8384.     }
  8385.     this._inner.RemoveObserver(observer);
  8386.   },
  8387.   
  8388.   ArcLabelsIn: function(node) {
  8389.     return this._inner.ArcLabelsIn(node);
  8390.   },
  8391.   
  8392.   ArcLabelsOut: function(source) {
  8393.     return this._inner.ArcLabelsOut(source);
  8394.   },
  8395.   
  8396.   GetAllResources: function() {
  8397.     return this._inner.GetAllResources();
  8398.   },
  8399.   
  8400.   IsCommandEnabled: function(sources, command, arguments) {
  8401.     return this._inner.IsCommandEnabled(sources, command, arguments);
  8402.   },
  8403.   
  8404.   DoCommand: function(sources, command, arguments) {
  8405.     this._inner.DoCommand(sources, command, arguments);
  8406.   },
  8407.   
  8408.   GetAllCmds: function(source) {
  8409.     return this._inner.GetAllCmds(source);
  8410.   },
  8411.   
  8412.   hasArcIn: function(node, arc) {
  8413.     return this._inner.hasArcIn(node, arc);
  8414.   },
  8415.   
  8416.   hasArcOut: function(source, arc) {
  8417.     return this._inner.hasArcOut(source, arc);
  8418.   },
  8419.   
  8420.   beginUpdateBatch: function() {
  8421.     return this._inner.beginUpdateBatch();
  8422.   },
  8423.   
  8424.   endUpdateBatch: function() {
  8425.     return this._inner.endUpdateBatch();
  8426.   },
  8427.   
  8428.   /**
  8429.    * See nsIRDFRemoteDataSource.idl
  8430.    */
  8431.   get loaded() {
  8432.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  8433.   },
  8434.   
  8435.   Init: function(uri) {
  8436.   },
  8437.   
  8438.   Refresh: function(blocking) {
  8439.   },
  8440.   
  8441.   Flush: function() {
  8442.     if (this._inner instanceof Components.interfaces.nsIRDFRemoteDataSource)
  8443.       this._inner.Flush();
  8444.   },
  8445.   
  8446.   FlushTo: function(uri) {
  8447.   },
  8448.   
  8449.   /**
  8450.    * See nsISupports.idl
  8451.    */
  8452.   QueryInterface: function(iid) {
  8453.     if (!iid.equals(Components.interfaces.nsIRDFDataSource) &&
  8454.         !iid.equals(Components.interfaces.nsIRDFRemoteDataSource) && 
  8455.         !iid.equals(Components.interfaces.nsISupports))
  8456.       throw Components.results.NS_ERROR_NO_INTERFACE;
  8457.     return this;
  8458.   }
  8459. };
  8460.  
  8461. function UpdateItem () {
  8462. }
  8463. UpdateItem.prototype = {
  8464.   /**
  8465.    * See nsIUpdateService.idl
  8466.    */
  8467.   init: function(id, version, installLocationKey, minAppVersion, maxAppVersion,
  8468.                  name, downloadURL, xpiHash, iconURL, updateURL, type) {
  8469.     this._id                  = id;
  8470.     this._version             = version;
  8471.     this._installLocationKey  = installLocationKey;
  8472.     this._minAppVersion       = minAppVersion;
  8473.     this._maxAppVersion       = maxAppVersion;
  8474.     this._name                = name;
  8475.     this._downloadURL         = downloadURL;
  8476.     this._xpiHash             = xpiHash;
  8477.     this._iconURL             = iconURL;
  8478.     this._updateURL           = updateURL;
  8479.     this._type                = type;
  8480.   },
  8481.   
  8482.   /**
  8483.    * See nsIUpdateService.idl
  8484.    */
  8485.   get id()                { return this._id;                },
  8486.   get version()           { return this._version;           },
  8487.   get installLocationKey(){ return this._installLocationKey;},
  8488.   get minAppVersion()     { return this._minAppVersion;     },
  8489.   get maxAppVersion()     { return this._maxAppVersion;     },
  8490.   get name()              { return this._name;              },
  8491.   get xpiURL()            { return this._downloadURL;       },
  8492.   get xpiHash()           { return this._xpiHash;           },
  8493.   get iconURL()           { return this._iconURL            },
  8494.   get updateRDF()         { return this._updateURL;         },
  8495.   get type()              { return this._type;              },
  8496.  
  8497.   /**
  8498.    * See nsIUpdateService.idl
  8499.    */
  8500.   get objectSource() {
  8501.     return { id                 : this._id, 
  8502.              version            : this._version, 
  8503.              installLocationKey : this._installLocationKey,
  8504.              minAppVersion      : this._minAppVersion,
  8505.              maxAppVersion      : this._maxAppVersion,
  8506.              name               : this._name, 
  8507.              xpiURL             : this._downloadURL, 
  8508.              xpiHash            : this._xpiHash,
  8509.              iconURL            : this._iconURL, 
  8510.              updateRDF          : this._updateURL,
  8511.              type               : this._type 
  8512.            }.toSource();
  8513.   },
  8514.   
  8515.   /**
  8516.    * See nsISupports.idl
  8517.    */
  8518.   QueryInterface: function(iid) {
  8519.     if (!iid.equals(Components.interfaces.nsIUpdateItem) &&
  8520.         !iid.equals(Components.interfaces.nsISupports))
  8521.       throw Components.results.NS_ERROR_NO_INTERFACE;
  8522.     return this;
  8523.   }
  8524. };
  8525.  
  8526. var gModule = {
  8527.   registerSelf: function(componentManager, fileSpec, location, type) {
  8528.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  8529.     
  8530.     for (var key in this._objects) {
  8531.       var obj = this._objects[key];
  8532.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  8533.                                                fileSpec, location, type);
  8534.     }
  8535.  
  8536.     // Make the Extension Manager a startup observer
  8537.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  8538.                                     .getService(Components.interfaces.nsICategoryManager);
  8539.     categoryManager.addCategoryEntry("app-startup", this._objects.manager.className,
  8540.                                      "service," + this._objects.manager.contractID, 
  8541.                                      true, true);
  8542.   },
  8543.   
  8544.   getClassObject: function(componentManager, cid, iid) {
  8545.     if (!iid.equals(Components.interfaces.nsIFactory))
  8546.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  8547.  
  8548.     for (var key in this._objects) {
  8549.       if (cid.equals(this._objects[key].CID))
  8550.         return this._objects[key].factory;
  8551.     }
  8552.     
  8553.     throw Components.results.NS_ERROR_NO_INTERFACE;
  8554.   },
  8555.   
  8556.   _makeFactory: #1= function(ctor) {
  8557.     return { 
  8558.              createInstance: function (outer, iid) {
  8559.                if (outer != null)
  8560.                  throw Components.results.NS_ERROR_NO_AGGREGATION;
  8561.                return (new ctor()).QueryInterface(iid);
  8562.              } 
  8563.            };  
  8564.   },
  8565.   
  8566.   _objects: {
  8567.     manager: { CID        : ExtensionManager.prototype.classID,
  8568.                contractID : ExtensionManager.prototype.contractID,
  8569.                className  : ExtensionManager.prototype.classDescription,
  8570.                factory    : #1#(ExtensionManager)
  8571.              },
  8572.     item:    { CID        : Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"),
  8573.                contractID : "@mozilla.org/updates/item;1",
  8574.                className  : "Update Item",
  8575.                factory    : #1#(UpdateItem)
  8576.              }
  8577.    },
  8578.  
  8579.   canUnload: function(componentManager) {
  8580.     return true;
  8581.   }
  8582. };
  8583.  
  8584. function NSGetModule(compMgr, fileSpec) {
  8585.   return gModule;
  8586. }
  8587.  
  8588.